@ttt-productions/q-core 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/constants/business.js +2 -2
  2. package/dist/constants/business.js.map +1 -1
  3. package/dist/constants/media.js +1 -1
  4. package/dist/constants/media.js.map +1 -1
  5. package/dist/constants/sports.js +1 -1
  6. package/dist/constants/sports.js.map +1 -1
  7. package/dist/constants/telemetry.js +3 -3
  8. package/dist/constants/telemetry.js.map +1 -1
  9. package/dist/doc-schemas/admin-notifications.js +3 -3
  10. package/dist/doc-schemas/admin-notifications.js.map +1 -1
  11. package/dist/doc-schemas/aggregate-stats.js +3 -3
  12. package/dist/doc-schemas/aggregate-stats.js.map +1 -1
  13. package/dist/doc-schemas/atoms.d.ts +1 -1
  14. package/dist/doc-schemas/atoms.js +3 -3
  15. package/dist/doc-schemas/atoms.js.map +1 -1
  16. package/dist/doc-schemas/audit-event.js +2 -2
  17. package/dist/doc-schemas/audit-event.js.map +1 -1
  18. package/dist/doc-schemas/firestore-primitives.js +1 -1
  19. package/dist/doc-schemas/firestore-primitives.js.map +1 -1
  20. package/dist/doc-schemas/game-location.js +2 -2
  21. package/dist/doc-schemas/game-location.js.map +1 -1
  22. package/dist/doc-schemas/game-stat-history.js +1 -1
  23. package/dist/doc-schemas/game-stat-history.js.map +1 -1
  24. package/dist/doc-schemas/game-stats.js +6 -6
  25. package/dist/doc-schemas/game-stats.js.map +1 -1
  26. package/dist/doc-schemas/league-team.js +4 -4
  27. package/dist/doc-schemas/league-team.js.map +1 -1
  28. package/dist/doc-schemas/membership-roster-sync.js +2 -2
  29. package/dist/doc-schemas/membership-roster-sync.js.map +1 -1
  30. package/dist/doc-schemas/pending-media.d.ts +6 -6
  31. package/dist/doc-schemas/publicUser.js +1 -1
  32. package/dist/doc-schemas/publicUser.js.map +1 -1
  33. package/dist/doc-schemas/registration-start.js +1 -1
  34. package/dist/doc-schemas/registration-start.js.map +1 -1
  35. package/dist/doc-schemas/registry.d.ts +3 -3
  36. package/dist/doc-schemas/sport-game.js +4 -4
  37. package/dist/doc-schemas/sport-game.js.map +1 -1
  38. package/dist/doc-schemas/sport-league.js +1 -1
  39. package/dist/doc-schemas/sport-league.js.map +1 -1
  40. package/dist/doc-schemas/stats-operation.js +2 -2
  41. package/dist/doc-schemas/stats-operation.js.map +1 -1
  42. package/dist/doc-schemas/status-reconcile-queue.js +1 -1
  43. package/dist/doc-schemas/status-reconcile-queue.js.map +1 -1
  44. package/dist/doc-schemas/sweep-state.js +2 -2
  45. package/dist/doc-schemas/sweep-state.js.map +1 -1
  46. package/dist/doc-schemas/team-membership.js +1 -1
  47. package/dist/doc-schemas/team-membership.js.map +1 -1
  48. package/dist/doc-schemas/user-profile.js +1 -1
  49. package/dist/doc-schemas/user-profile.js.map +1 -1
  50. package/dist/doc-schemas/waiver-template.js +1 -1
  51. package/dist/doc-schemas/waiver-template.js.map +1 -1
  52. package/dist/doc-schemas/waivers-and-hardware.js +3 -3
  53. package/dist/doc-schemas/waivers-and-hardware.js.map +1 -1
  54. package/dist/domain-events/domain-events.js +2 -2
  55. package/dist/domain-events/domain-events.js.map +1 -1
  56. package/dist/ids/award-grant-ids.js +1 -1
  57. package/dist/ids/award-grant-ids.js.map +1 -1
  58. package/dist/ids/compound-ids.js +1 -1
  59. package/dist/ids/compound-ids.js.map +1 -1
  60. package/dist/ids/minted-ids.js +1 -1
  61. package/dist/ids/minted-ids.js.map +1 -1
  62. package/dist/ids/notification-ids.js +1 -1
  63. package/dist/ids/notification-ids.js.map +1 -1
  64. package/dist/ids/stat-event-ids.d.ts +1 -1
  65. package/dist/ids/stat-event-ids.js +2 -2
  66. package/dist/ids/stat-event-ids.js.map +1 -1
  67. package/dist/ids/stats-operation-ids.js +1 -1
  68. package/dist/ids/stats-operation-ids.js.map +1 -1
  69. package/dist/index.js +1 -1
  70. package/dist/index.js.map +1 -1
  71. package/dist/input-formats/input-formats.js +1 -1
  72. package/dist/input-formats/input-formats.js.map +1 -1
  73. package/dist/media/file-origins.js +1 -1
  74. package/dist/media/file-origins.js.map +1 -1
  75. package/dist/media/picture-url.js +1 -1
  76. package/dist/media/picture-url.js.map +1 -1
  77. package/dist/migration-console/action-ids.js +1 -1
  78. package/dist/migration-console/action-ids.js.map +1 -1
  79. package/dist/migration-console/manifest.js +1 -1
  80. package/dist/migration-console/manifest.js.map +1 -1
  81. package/dist/notifications/admin-notification-config.js +1 -1
  82. package/dist/notifications/admin-notification-config.js.map +1 -1
  83. package/dist/notifications/occurrences.js +1 -1
  84. package/dist/notifications/occurrences.js.map +1 -1
  85. package/dist/paths/collections.js +3 -3
  86. package/dist/paths/collections.js.map +1 -1
  87. package/dist/paths/picture-owner-paths.js +1 -1
  88. package/dist/paths/picture-owner-paths.js.map +1 -1
  89. package/dist/paths/storage-paths.d.ts +1 -1
  90. package/dist/paths/storage-paths.js +1 -1
  91. package/dist/paths/storage-paths.js.map +1 -1
  92. package/dist/paths/utils.d.ts +1 -1
  93. package/dist/paths/utils.js +1 -1
  94. package/dist/paths/utils.js.map +1 -1
  95. package/dist/schemas/admin-notifications.js +1 -1
  96. package/dist/schemas/admin-notifications.js.map +1 -1
  97. package/dist/schemas/app-config.js +1 -1
  98. package/dist/schemas/app-config.js.map +1 -1
  99. package/dist/schemas/awards.js +1 -1
  100. package/dist/schemas/awards.js.map +1 -1
  101. package/dist/schemas/dead-letters.js +2 -2
  102. package/dist/schemas/dead-letters.js.map +1 -1
  103. package/dist/schemas/leagues.js +1 -1
  104. package/dist/schemas/leagues.js.map +1 -1
  105. package/dist/schemas/migration-console.js +1 -1
  106. package/dist/schemas/migration-console.js.map +1 -1
  107. package/dist/schemas/pictures.js +1 -1
  108. package/dist/schemas/pictures.js.map +1 -1
  109. package/dist/schemas/schedules.js +2 -2
  110. package/dist/schemas/schedules.js.map +1 -1
  111. package/dist/schemas/scoring.js +1 -1
  112. package/dist/schemas/scoring.js.map +1 -1
  113. package/dist/schemas/signed-waivers.js +1 -1
  114. package/dist/schemas/signed-waivers.js.map +1 -1
  115. package/dist/schemas/stats.js +6 -6
  116. package/dist/schemas/stats.js.map +1 -1
  117. package/dist/schemas/team-joining.js +3 -3
  118. package/dist/schemas/team-joining.js.map +1 -1
  119. package/dist/schemas/teams.js +7 -7
  120. package/dist/schemas/teams.js.map +1 -1
  121. package/dist/schemas/users.js +3 -3
  122. package/dist/schemas/users.js.map +1 -1
  123. package/dist/sports/basketball-scoring.js +4 -4
  124. package/dist/sports/basketball-scoring.js.map +1 -1
  125. package/dist/sports/bulk-entry.js +1 -1
  126. package/dist/sports/bulk-entry.js.map +1 -1
  127. package/dist/sports/contribution.d.ts +5 -5
  128. package/dist/sports/contribution.d.ts.map +1 -1
  129. package/dist/sports/contribution.js +4 -4
  130. package/dist/sports/contribution.js.map +1 -1
  131. package/dist/sports/curling-scoring.js +2 -2
  132. package/dist/sports/curling-scoring.js.map +1 -1
  133. package/dist/sports/football-scoring.js +2 -2
  134. package/dist/sports/football-scoring.js.map +1 -1
  135. package/dist/sports/index.js +1 -1
  136. package/dist/sports/index.js.map +1 -1
  137. package/dist/sports/ledger-continuity.js +1 -1
  138. package/dist/sports/ledger-continuity.js.map +1 -1
  139. package/dist/sports/registry.js +2 -2
  140. package/dist/sports/registry.js.map +1 -1
  141. package/dist/sports/replay.js +1 -1
  142. package/dist/sports/replay.js.map +1 -1
  143. package/dist/sports/rollup-check.js +1 -1
  144. package/dist/sports/rollup-check.js.map +1 -1
  145. package/dist/sports/score-mirror.js +1 -1
  146. package/dist/sports/score-mirror.js.map +1 -1
  147. package/dist/sports/scoring-display.d.ts +1 -1
  148. package/dist/sports/scoring-display.js +2 -2
  149. package/dist/sports/scoring-display.js.map +1 -1
  150. package/dist/sports/scoring.d.ts +5 -5
  151. package/dist/sports/scoring.d.ts.map +1 -1
  152. package/dist/sports/scoring.js +2 -2
  153. package/dist/sports/scoring.js.map +1 -1
  154. package/dist/sports/sport-adapter.d.ts +3 -3
  155. package/dist/sports/sport-adapter.d.ts.map +1 -1
  156. package/dist/sports/sport-adapter.js +2 -2
  157. package/dist/sports/sport-adapter.js.map +1 -1
  158. package/dist/sports/store-driver.d.ts +1 -1
  159. package/dist/sports/store-driver.d.ts.map +1 -1
  160. package/dist/sports/store-driver.js +2 -2
  161. package/dist/sports/store-driver.js.map +1 -1
  162. package/dist/types/audit.js +4 -4
  163. package/dist/types/audit.js.map +1 -1
  164. package/dist/types/claims.js +1 -1
  165. package/dist/types/claims.js.map +1 -1
  166. package/dist/types/picture-owners.js +1 -1
  167. package/dist/types/picture-owners.js.map +1 -1
  168. package/dist/types/schedules.js +1 -1
  169. package/dist/types/schedules.js.map +1 -1
  170. package/dist/types/sports.js +2 -2
  171. package/dist/types/sports.js.map +1 -1
  172. package/dist/types/stats.js +5 -5
  173. package/dist/types/stats.js.map +1 -1
  174. package/dist/types/unions.js +2 -2
  175. package/dist/types/unions.js.map +1 -1
  176. package/dist/types/uploads.js +1 -1
  177. package/dist/types/uploads.js.map +1 -1
  178. package/dist/upload-variables/sign-hardware-contract-variables.js +1 -1
  179. package/dist/upload-variables/sign-hardware-contract-variables.js.map +1 -1
  180. package/dist/upload-variables/sign-waiver-variables.js +1 -1
  181. package/dist/upload-variables/sign-waiver-variables.js.map +1 -1
  182. package/dist/utils/account-status.js +1 -1
  183. package/dist/utils/account-status.js.map +1 -1
  184. package/dist/utils/app-routes.js +1 -1
  185. package/dist/utils/app-routes.js.map +1 -1
  186. package/dist/utils/league-join-warning.js +1 -1
  187. package/dist/utils/league-join-warning.js.map +1 -1
  188. package/dist/utils/league-joining.js +1 -1
  189. package/dist/utils/league-joining.js.map +1 -1
  190. package/dist/utils/league-mode.js +1 -1
  191. package/dist/utils/league-mode.js.map +1 -1
  192. package/dist/utils/team-creation.js +1 -1
  193. package/dist/utils/team-creation.js.map +1 -1
  194. package/dist/utils/team-member-actions.js +1 -1
  195. package/dist/utils/team-member-actions.js.map +1 -1
  196. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"football-scoring.js","sourceRoot":"","sources":["../../src/sports/football-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The football adapter's scoring, display, and bulk-entry members. Football is team-scored: each\n// event adds its points to one team's score, no event names a player, and there is no finish rule —\n// the game is finalized by hand. Bulk entry takes each team's final score.\n//\n// `apply` and `undo` are one function over an event's points with opposite signs, so they are exact\n// inverses by construction (SPORT-103).\n\nimport { z } from 'zod';\nimport {\n FOOTBALL_POINTS_BY_EVENT_TYPE,\n MAX_STAT_EVENTS_PER_WRITE,\n TEAM_NOT_IN_GAME_MESSAGE,\n} from '../constants/sports.js';\nimport type { FootballTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { FootballGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport {\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\n FOOTBALL_ONE_POINT_EVENT_TYPE,\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n FOOTBALL_SIX_POINT_EVENT_TYPE,\n FOOTBALL_STAT_EVENT_TYPE_KEYS,\n FOOTBALL_TEAM_STAT_KEYS,\n FOOTBALL_TWO_POINT_EVENT_TYPE,\n} from '../types/sports.js';\nimport type { FootballStatEventType, FootballTeamStatKey } from '../types/sports.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type StatEventDraft,\n type TeamScoresBulkEntry,\n type TeamScoresValues,\n} from './bulk-entry.js';\nimport {\n buildGameContribution,\n decideParticipation,\n type ContributionSpec,\n} from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type SportScoring,\n type SportStatEventBody,\n type StatCorrectionBody,\n type StatEventBody,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n formatSignedValue,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n type TeamPointsScoringPanel,\n} from './scoring-display.js';\n\ntype FootballEvent = Extract<SportStatEventBody, { type: FootballStatEventType }>;\n\nfunction isFootballEvent(event: StatEventBody | undefined): event is FootballEvent {\n return isSportStatEventIn(FOOTBALL_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// football game's (SPORT-002).\nfunction ownStats(state: GameStats): FootballGameStats {\n return state as FootballGameStats;\n}\n\nexport function emptyFootballTeamLine(): FootballTeamStats {\n return Object.fromEntries(FOOTBALL_TEAM_STAT_KEYS.map((key) => [key, 0])) as FootballTeamStats;\n}\n\nfunction teamScoreOf(state: FootballGameStats, teamId: string): number {\n return (state.teamStats[teamId] ?? emptyFootballTeamLine()).score;\n}\n\n/** The points an event adds to its team's score: a scoring event's own, or a bulk edit's `value`. */\nfunction pointsOf(event: FootballEvent): number {\n return event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE\n ? event.value\n : FOOTBALL_POINTS_BY_EVENT_TYPE[event.type];\n}\n\nfunction addToScore(stateIn: GameStats, teamId: string, amount: number): FootballGameStats {\n const state = ownStats(stateIn);\n const line = state.teamStats[teamId] ?? emptyFootballTeamLine();\n return {\n teamStats: { ...state.teamStats, [teamId]: { ...line, score: line.score + amount } },\n playerStats: state.playerStats,\n };\n}\n\n// A correction to the team's score; football keeps no player line to correct.\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: FOOTBALL_TEAM_STAT_KEYS, player: [] };\n\nfunction correctionAmount(event: StatCorrectionBody): number {\n const fields =\n event.playerUserId === undefined ? CORRECTABLE_FIELDS.team : CORRECTABLE_FIELDS.player;\n if (!fields.includes(event.stat)) {\n throw new Error(\n `A football correction cannot change ${event.stat} on a ${event.playerUserId === undefined ? 'team' : 'player'} line.`,\n );\n }\n return event.value;\n}\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nconst validate: SportScoring['validate'] = (stateIn, event, roster) => {\n if (!isFootballEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (!roster.teamIds.includes(event.teamId)) return refuseStatEvent('teamNotInGame');\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\n if (!Number.isInteger(event.value) || event.value === 0)\n return refuseStatEvent('bulkValueInvalid');\n const score = teamScoreOf(ownStats(stateIn), event.teamId);\n if (event.previousValue !== score) return refuseStatEvent('bulkStale');\n if (score + event.value < 0) return refuseStatEvent('bulkValueInvalid');\n }\n return STAT_EVENT_ACCEPTED;\n};\n\n// What a completed football game adds: the standings line from the two scores, and a game played\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\n// absence withholds a game played and no entry is ever non-scoring.\nconst FOOTBALL_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: [],\n teamSeasonPlayerSums: [],\n zeroLineIsNonScoring: false,\n deriveLine: (_kind, line) => line,\n};\n\nexport const FOOTBALL_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) return addToScore(state, event.teamId, correctionAmount(event));\n if (!isFootballEvent(event))\n throw new Error(`The football adapter does not apply ${event.type}.`);\n return addToScore(state, event.teamId, pointsOf(event));\n },\n undo: (state, target) => {\n if (!isFootballEvent(target))\n throw new Error(`The football adapter does not undo ${target.type}.`);\n return addToScore(state, target.teamId, -pointsOf(target));\n },\n // A football line has no derived field.\n deriveLine: FOOTBALL_CONTRIBUTION.deriveLine,\n correctableFields: CORRECTABLE_FIELDS,\n // No rounds and no finish rule: the game is finalized by hand.\n nextRound: () => null,\n finalizationReadiness: () => FINALIZATION_READY,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(FOOTBALL_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(FOOTBALL_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const FOOTBALL_EVENT_LABELS = {\n [FOOTBALL_SIX_POINT_EVENT_TYPE]: { button: '+6', play: '6 points' },\n [FOOTBALL_TWO_POINT_EVENT_TYPE]: { button: '+2', play: '2 points' },\n [FOOTBALL_ONE_POINT_EVENT_TYPE]: { button: '+1', play: '1 point' },\n [FOOTBALL_BULK_EDIT_EVENT_TYPE]: { button: 'Final scores', play: 'Final score entry' },\n} as const satisfies Record<FootballStatEventType, StatEventLabel>;\n\nexport const FOOTBALL_FIELD_LABELS = {\n team: { score: 'Score' },\n player: {},\n} as const satisfies StatFieldLabels<FootballTeamStatKey, never>;\n\nexport function describeFootballEvent(\n event: GameStatEvent,\n resolve: StatEventNameResolver,\n): string {\n const storeEvent = describeStoreEvent(event, resolve, FOOTBALL_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isFootballEvent(event)) return event.type;\n const team = resolve.team(event.teamId);\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\n return `${team}: ${formatSignedValue(event.value)} points (${FOOTBALL_EVENT_LABELS[event.type].play.toLowerCase()})`;\n }\n return `${team}: ${FOOTBALL_EVENT_LABELS[event.type].play}`;\n}\n\nexport const FOOTBALL_SCOREBOARD: Scoreboard = {\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\n jumpBall: false,\n};\n\nexport const FOOTBALL_SCORING_PANEL: TeamPointsScoringPanel = {\n kind: 'teamPoints',\n eventTypes: FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — each team's final score\n// ---------------------------------------------------------------------------------------------\n\nfunction scoreDrafts(values: TeamScoresValues, context: BulkEntryContext): StatEventDraft[] {\n const state = ownStats(context.state);\n const drafts: StatEventDraft[] = [];\n for (const teamId of context.game.teamIds) {\n const entered = values.scores[teamId];\n const previousValue = teamScoreOf(state, teamId);\n if (entered === undefined || entered === previousValue) continue;\n drafts.push({\n type: FOOTBALL_BULK_EDIT_EVENT_TYPE,\n teamId,\n value: entered - previousValue,\n previousValue,\n });\n }\n return drafts;\n}\n\nexport const FOOTBALL_BULK_ENTRY: TeamScoresBulkEntry = {\n kind: 'teamScores',\n formDefaults: (context) => ({\n scores: Object.fromEntries(\n context.game.teamIds.map((teamId) => [teamId, teamScoreOf(ownStats(context.state), teamId)]),\n ),\n }),\n formSchema: (context) => {\n const teams = new Set(context.game.teamIds);\n return z\n .object({ scores: z.record(z.string(), bulkEntryCountField) })\n .superRefine((values, issues) => {\n for (const teamId of Object.keys(values.scores)) {\n if (!teams.has(teamId))\n issues.addIssue({\n code: 'custom',\n path: ['scores', teamId],\n message: TEAM_NOT_IN_GAME_MESSAGE,\n });\n }\n if (scoreDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({\n code: 'custom',\n path: ['scores'],\n message: BULK_ENTRY_TOO_LARGE_MESSAGE,\n });\n }\n });\n },\n toEvents: scoreDrafts,\n};\n"]}
1
+ {"version":3,"file":"football-scoring.js","sourceRoot":"","sources":["../../src/sports/football-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The football adapter's scoring, display, and bulk-entry members. Football is team-scored: each\n// event adds its points to one team's score, no event names a player, and there is no finish rule —\n// the game is finalized by hand. Bulk entry takes each team's final score.\n//\n// `apply` and `undo` are one function over an event's points with opposite signs, so they are exact\n// inverses by construction (RULE-QS-SPORT-913).\n\nimport { z } from 'zod';\nimport {\n FOOTBALL_POINTS_BY_EVENT_TYPE,\n MAX_STAT_EVENTS_PER_WRITE,\n TEAM_NOT_IN_GAME_MESSAGE,\n} from '../constants/sports.js';\nimport type { FootballTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { FootballGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport {\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\n FOOTBALL_ONE_POINT_EVENT_TYPE,\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n FOOTBALL_SIX_POINT_EVENT_TYPE,\n FOOTBALL_STAT_EVENT_TYPE_KEYS,\n FOOTBALL_TEAM_STAT_KEYS,\n FOOTBALL_TWO_POINT_EVENT_TYPE,\n} from '../types/sports.js';\nimport type { FootballStatEventType, FootballTeamStatKey } from '../types/sports.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type StatEventDraft,\n type TeamScoresBulkEntry,\n type TeamScoresValues,\n} from './bulk-entry.js';\nimport {\n buildGameContribution,\n decideParticipation,\n type ContributionSpec,\n} from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type SportScoring,\n type SportStatEventBody,\n type StatCorrectionBody,\n type StatEventBody,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n formatSignedValue,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n type TeamPointsScoringPanel,\n} from './scoring-display.js';\n\ntype FootballEvent = Extract<SportStatEventBody, { type: FootballStatEventType }>;\n\nfunction isFootballEvent(event: StatEventBody | undefined): event is FootballEvent {\n return isSportStatEventIn(FOOTBALL_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// football game's (RULE-QS-SPORT-902).\nfunction ownStats(state: GameStats): FootballGameStats {\n return state as FootballGameStats;\n}\n\nexport function emptyFootballTeamLine(): FootballTeamStats {\n return Object.fromEntries(FOOTBALL_TEAM_STAT_KEYS.map((key) => [key, 0])) as FootballTeamStats;\n}\n\nfunction teamScoreOf(state: FootballGameStats, teamId: string): number {\n return (state.teamStats[teamId] ?? emptyFootballTeamLine()).score;\n}\n\n/** The points an event adds to its team's score: a scoring event's own, or a bulk edit's `value`. */\nfunction pointsOf(event: FootballEvent): number {\n return event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE\n ? event.value\n : FOOTBALL_POINTS_BY_EVENT_TYPE[event.type];\n}\n\nfunction addToScore(stateIn: GameStats, teamId: string, amount: number): FootballGameStats {\n const state = ownStats(stateIn);\n const line = state.teamStats[teamId] ?? emptyFootballTeamLine();\n return {\n teamStats: { ...state.teamStats, [teamId]: { ...line, score: line.score + amount } },\n playerStats: state.playerStats,\n };\n}\n\n// A correction to the team's score; football keeps no player line to correct.\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: FOOTBALL_TEAM_STAT_KEYS, player: [] };\n\nfunction correctionAmount(event: StatCorrectionBody): number {\n const fields =\n event.playerUserId === undefined ? CORRECTABLE_FIELDS.team : CORRECTABLE_FIELDS.player;\n if (!fields.includes(event.stat)) {\n throw new Error(\n `A football correction cannot change ${event.stat} on a ${event.playerUserId === undefined ? 'team' : 'player'} line.`,\n );\n }\n return event.value;\n}\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nconst validate: SportScoring['validate'] = (stateIn, event, roster) => {\n if (!isFootballEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (!roster.teamIds.includes(event.teamId)) return refuseStatEvent('teamNotInGame');\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\n if (!Number.isInteger(event.value) || event.value === 0)\n return refuseStatEvent('bulkValueInvalid');\n const score = teamScoreOf(ownStats(stateIn), event.teamId);\n if (event.previousValue !== score) return refuseStatEvent('bulkStale');\n if (score + event.value < 0) return refuseStatEvent('bulkValueInvalid');\n }\n return STAT_EVENT_ACCEPTED;\n};\n\n// What a completed football game adds: the standings line from the two scores, and a game played\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\n// absence withholds a game played and no entry is ever non-scoring.\nconst FOOTBALL_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: [],\n teamSeasonPlayerSums: [],\n zeroLineIsNonScoring: false,\n deriveLine: (_kind, line) => line,\n};\n\nexport const FOOTBALL_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) return addToScore(state, event.teamId, correctionAmount(event));\n if (!isFootballEvent(event))\n throw new Error(`The football adapter does not apply ${event.type}.`);\n return addToScore(state, event.teamId, pointsOf(event));\n },\n undo: (state, target) => {\n if (!isFootballEvent(target))\n throw new Error(`The football adapter does not undo ${target.type}.`);\n return addToScore(state, target.teamId, -pointsOf(target));\n },\n // A football line has no derived field.\n deriveLine: FOOTBALL_CONTRIBUTION.deriveLine,\n correctableFields: CORRECTABLE_FIELDS,\n // No rounds and no finish rule: the game is finalized by hand.\n nextRound: () => null,\n finalizationReadiness: () => FINALIZATION_READY,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(FOOTBALL_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(FOOTBALL_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const FOOTBALL_EVENT_LABELS = {\n [FOOTBALL_SIX_POINT_EVENT_TYPE]: { button: '+6', play: '6 points' },\n [FOOTBALL_TWO_POINT_EVENT_TYPE]: { button: '+2', play: '2 points' },\n [FOOTBALL_ONE_POINT_EVENT_TYPE]: { button: '+1', play: '1 point' },\n [FOOTBALL_BULK_EDIT_EVENT_TYPE]: { button: 'Final scores', play: 'Final score entry' },\n} as const satisfies Record<FootballStatEventType, StatEventLabel>;\n\nexport const FOOTBALL_FIELD_LABELS = {\n team: { score: 'Score' },\n player: {},\n} as const satisfies StatFieldLabels<FootballTeamStatKey, never>;\n\nexport function describeFootballEvent(\n event: GameStatEvent,\n resolve: StatEventNameResolver,\n): string {\n const storeEvent = describeStoreEvent(event, resolve, FOOTBALL_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isFootballEvent(event)) return event.type;\n const team = resolve.team(event.teamId);\n if (event.type === FOOTBALL_BULK_EDIT_EVENT_TYPE) {\n return `${team}: ${formatSignedValue(event.value)} points (${FOOTBALL_EVENT_LABELS[event.type].play.toLowerCase()})`;\n }\n return `${team}: ${FOOTBALL_EVENT_LABELS[event.type].play}`;\n}\n\nexport const FOOTBALL_SCOREBOARD: Scoreboard = {\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\n jumpBall: false,\n};\n\nexport const FOOTBALL_SCORING_PANEL: TeamPointsScoringPanel = {\n kind: 'teamPoints',\n eventTypes: FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — each team's final score\n// ---------------------------------------------------------------------------------------------\n\nfunction scoreDrafts(values: TeamScoresValues, context: BulkEntryContext): StatEventDraft[] {\n const state = ownStats(context.state);\n const drafts: StatEventDraft[] = [];\n for (const teamId of context.game.teamIds) {\n const entered = values.scores[teamId];\n const previousValue = teamScoreOf(state, teamId);\n if (entered === undefined || entered === previousValue) continue;\n drafts.push({\n type: FOOTBALL_BULK_EDIT_EVENT_TYPE,\n teamId,\n value: entered - previousValue,\n previousValue,\n });\n }\n return drafts;\n}\n\nexport const FOOTBALL_BULK_ENTRY: TeamScoresBulkEntry = {\n kind: 'teamScores',\n formDefaults: (context) => ({\n scores: Object.fromEntries(\n context.game.teamIds.map((teamId) => [teamId, teamScoreOf(ownStats(context.state), teamId)]),\n ),\n }),\n formSchema: (context) => {\n const teams = new Set(context.game.teamIds);\n return z\n .object({ scores: z.record(z.string(), bulkEntryCountField) })\n .superRefine((values, issues) => {\n for (const teamId of Object.keys(values.scores)) {\n if (!teams.has(teamId))\n issues.addIssue({\n code: 'custom',\n path: ['scores', teamId],\n message: TEAM_NOT_IN_GAME_MESSAGE,\n });\n }\n if (scoreDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({\n code: 'custom',\n path: ['scores'],\n message: BULK_ENTRY_TOO_LARGE_MESSAGE,\n });\n }\n });\n },\n toEvents: scoreDrafts,\n};\n"]}
@@ -1,4 +1,4 @@
1
- // The sport adapters. A sport is selected only through the registry (SPORT-002), so the individual
1
+ // The sport adapters. A sport is selected only through the registry (RULE-QS-SPORT-902), so the individual
2
2
  // adapters are not exported.
3
3
  export * from './column-catalog.js';
4
4
  export * from './sport-adapter.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sports/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,6BAA6B;AAE7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAMlC,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// The sport adapters. A sport is selected only through the registry (SPORT-002), so the individual\n// adapters are not exported.\n\nexport * from './column-catalog.js';\nexport * from './sport-adapter.js';\nexport * from './shared-columns.js';\nexport * from './scoring.js';\nexport * from './scoring-display.js';\nexport * from './bulk-entry.js';\nexport * from './replay.js';\nexport * from './ledger-continuity.js';\nexport * from './store-driver.js';\nexport * from './contribution.js';\nexport * from './finalized-record.js';\nexport * from './rankings.js';\nexport * from './rollup-check.js';\nexport type {\n LifetimeSummary,\n LifetimeSummaryAverage,\n LifetimeSummaryTotal,\n} from './lifetime-summary.js';\nexport { basketballFantasyPoints } from './basketball-scoring.js';\nexport {\n SPORT_ADAPTERS,\n SPORT_ADAPTER_LIST,\n UnknownSportError,\n getSportAdapter,\n} from './registry.js';\nexport { gameScoreMirror } from './score-mirror.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sports/index.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,6BAA6B;AAE7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAMlC,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// The sport adapters. A sport is selected only through the registry (RULE-QS-SPORT-902), so the individual\n// adapters are not exported.\n\nexport * from './column-catalog.js';\nexport * from './sport-adapter.js';\nexport * from './shared-columns.js';\nexport * from './scoring.js';\nexport * from './scoring-display.js';\nexport * from './bulk-entry.js';\nexport * from './replay.js';\nexport * from './ledger-continuity.js';\nexport * from './store-driver.js';\nexport * from './contribution.js';\nexport * from './finalized-record.js';\nexport * from './rankings.js';\nexport * from './rollup-check.js';\nexport type {\n LifetimeSummary,\n LifetimeSummaryAverage,\n LifetimeSummaryTotal,\n} from './lifetime-summary.js';\nexport { basketballFantasyPoints } from './basketball-scoring.js';\nexport {\n SPORT_ADAPTERS,\n SPORT_ADAPTER_LIST,\n UnknownSportError,\n getSportAdapter,\n} from './registry.js';\nexport { gameScoreMirror } from './score-mirror.js';\n"]}
@@ -1,7 +1,7 @@
1
1
  // Whether a game's event ledger holds exactly the sequences 1..n. `statEventSequence` is both the
2
2
  // number of events and the last sequence given, so a gap or a duplicate is out-of-band damage: the
3
3
  // check reports it, and finalize and the restore refuse it. The event documents are the source of
4
- // truth (SPORT-101), so nothing renumbers them to close a gap.
4
+ // truth (RULE-QS-SPORT-911), so nothing renumbers them to close a gap.
5
5
  /** `null` when the events hold exactly the sequences 1..n (an empty ledger included); otherwise the first break. */
6
6
  export function findLedgerContinuityBreak(events) {
7
7
  const sequences = events.map((event) => event.sequence).sort((left, right) => left - right);
@@ -1 +1 @@
1
- {"version":3,"file":"ledger-continuity.js","sourceRoot":"","sources":["../../src/sports/ledger-continuity.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,mGAAmG;AACnG,kGAAkG;AAClG,+DAA+D;AAO/D,oHAAoH;AACpH,MAAM,UAAU,yBAAyB,CACvC,MAAgD;IAEhD,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;IAC5F,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,QAAQ,KAAK,QAAQ;YAAE,SAAS;QACpC,IAAI,KAAK,GAAG,CAAC,IAAI,QAAQ,KAAK,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;QAC3F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["// Whether a game's event ledger holds exactly the sequences 1..n. `statEventSequence` is both the\n// number of events and the last sequence given, so a gap or a duplicate is out-of-band damage: the\n// check reports it, and finalize and the restore refuse it. The event documents are the source of\n// truth (SPORT-101), so nothing renumbers them to close a gap.\n\n/** The first place a ledger departs from 1..n: a sequence missing, or one held by two events. */\nexport type LedgerContinuityBreak =\n | { readonly kind: 'gap'; readonly missingSequence: number }\n | { readonly kind: 'duplicate'; readonly sequence: number };\n\n/** `null` when the events hold exactly the sequences 1..n (an empty ledger included); otherwise the first break. */\nexport function findLedgerContinuityBreak(\n events: readonly { readonly sequence: number }[],\n): LedgerContinuityBreak | null {\n const sequences = events.map((event) => event.sequence).sort((left, right) => left - right);\n for (let index = 0; index < sequences.length; index += 1) {\n const expected = index + 1;\n const sequence = sequences[index];\n if (sequence === expected) continue;\n if (index > 0 && sequence === sequences[index - 1]) return { kind: 'duplicate', sequence };\n return { kind: 'gap', missingSequence: expected };\n }\n return null;\n}\n"]}
1
+ {"version":3,"file":"ledger-continuity.js","sourceRoot":"","sources":["../../src/sports/ledger-continuity.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,mGAAmG;AACnG,kGAAkG;AAClG,uEAAuE;AAOvE,oHAAoH;AACpH,MAAM,UAAU,yBAAyB,CACvC,MAAgD;IAEhD,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;IAC5F,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,QAAQ,KAAK,QAAQ;YAAE,SAAS;QACpC,IAAI,KAAK,GAAG,CAAC,IAAI,QAAQ,KAAK,SAAS,CAAC,KAAK,GAAG,CAAC,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;QAC3F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["// Whether a game's event ledger holds exactly the sequences 1..n. `statEventSequence` is both the\n// number of events and the last sequence given, so a gap or a duplicate is out-of-band damage: the\n// check reports it, and finalize and the restore refuse it. The event documents are the source of\n// truth (RULE-QS-SPORT-911), so nothing renumbers them to close a gap.\n\n/** The first place a ledger departs from 1..n: a sequence missing, or one held by two events. */\nexport type LedgerContinuityBreak =\n | { readonly kind: 'gap'; readonly missingSequence: number }\n | { readonly kind: 'duplicate'; readonly sequence: number };\n\n/** `null` when the events hold exactly the sequences 1..n (an empty ledger included); otherwise the first break. */\nexport function findLedgerContinuityBreak(\n events: readonly { readonly sequence: number }[],\n): LedgerContinuityBreak | null {\n const sequences = events.map((event) => event.sequence).sort((left, right) => left - right);\n for (let index = 0; index < sequences.length; index += 1) {\n const expected = index + 1;\n const sequence = sequences[index];\n if (sequence === expected) continue;\n if (index > 0 && sequence === sequences[index - 1]) return { kind: 'duplicate', sequence };\n return { kind: 'gap', missingSequence: expected };\n }\n return null;\n}\n"]}
@@ -1,5 +1,5 @@
1
- // The sport registry: the only way a sport's adapter is selected, always by `leagueType` (SPORT-002).
2
- // A `LeagueType` member with no adapter does not compile (SPORT-005), and a value outside the union
1
+ // The sport registry: the only way a sport's adapter is selected, always by `leagueType` (RULE-QS-SPORT-902).
2
+ // A `LeagueType` member with no adapter does not compile (RULE-QS-SPORT-905), and a value outside the union
3
3
  // has no adapter — there is no default sport.
4
4
  import { LEAGUE_TYPE_KEYS } from '../types/sports.js';
5
5
  import { BASKETBALL_ADAPTER } from './basketball.js';
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/sports/registry.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,oGAAoG;AACpG,8CAA8C;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,MAAM,CAAC,MAAM,cAAc,GAAyB;IAClD,UAAU,EAAE,kBAAkB;IAC9B,OAAO,EAAE,eAAe;IACxB,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF,gGAAgG;AAChG,MAAM,CAAC,MAAM,kBAAkB,GAA+B,gBAAgB,CAAC,GAAG,CAChF,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,CAC3C,CAAC;AAEF;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,UAAU,CAAU;IAE7B,YAAY,UAAmB;QAC7B,KAAK,CAAC,iDAAiD,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,KAAK,CAAC;IACtB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,gBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC9F,CAAC;AAQD,MAAM,UAAU,eAAe,CAAC,UAAmB;IACjD,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACvE,OAAO,cAAc,CAAC,UAAU,CAAC,CAAC;AACpC,CAAC","sourcesContent":["// The sport registry: the only way a sport's adapter is selected, always by `leagueType` (SPORT-002).\n// A `LeagueType` member with no adapter does not compile (SPORT-005), and a value outside the union\n// has no adapter — there is no default sport.\n\nimport { LEAGUE_TYPE_KEYS } from '../types/sports.js';\nimport type { LeagueType } from '../types/sports.js';\nimport { BASKETBALL_ADAPTER } from './basketball.js';\nimport { CURLING_ADAPTER } from './curling.js';\nimport { FOOTBALL_ADAPTER } from './football.js';\nimport type { AnySportAdapter, SportAdapterRegistry } from './sport-adapter.js';\n\nexport const SPORT_ADAPTERS: SportAdapterRegistry = {\n Basketball: BASKETBALL_ADAPTER,\n Curling: CURLING_ADAPTER,\n Football: FOOTBALL_ADAPTER,\n};\n\n/** Every adapter, in `LEAGUE_TYPE_KEYS` order: for every surface that lists or picks sports. */\nexport const SPORT_ADAPTER_LIST: readonly AnySportAdapter[] = LEAGUE_TYPE_KEYS.map(\n (leagueType) => SPORT_ADAPTERS[leagueType],\n);\n\n/**\n * A `leagueType` with no registered adapter: missing, `null`, a wrong case, or any value outside the\n * union. It is a defect, never a designed refusal — every stored league and game carries a valid\n * `leagueType` — so it surfaces as an unexpected fault.\n */\nexport class UnknownSportError extends Error {\n readonly leagueType: unknown;\n\n constructor(leagueType: unknown) {\n super(`No sport adapter is registered for leagueType ${describeValue(leagueType)}.`);\n this.name = 'UnknownSportError';\n this.leagueType = leagueType;\n }\n}\n\nfunction describeValue(value: unknown): string {\n if (typeof value === 'string') return JSON.stringify(value);\n try {\n return String(value);\n } catch {\n return typeof value;\n }\n}\n\nfunction isLeagueType(value: unknown): value is LeagueType {\n return typeof value === 'string' && (LEAGUE_TYPE_KEYS as readonly string[]).includes(value);\n}\n\n/**\n * The adapter of `leagueType` as read off a document. Throws `UnknownSportError` for any value\n * outside the union.\n */\nexport function getSportAdapter<L extends LeagueType>(leagueType: L): SportAdapterRegistry[L];\nexport function getSportAdapter(leagueType: unknown): AnySportAdapter;\nexport function getSportAdapter(leagueType: unknown): AnySportAdapter {\n if (!isLeagueType(leagueType)) throw new UnknownSportError(leagueType);\n return SPORT_ADAPTERS[leagueType];\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/sports/registry.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,4GAA4G;AAC5G,8CAA8C;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,MAAM,CAAC,MAAM,cAAc,GAAyB;IAClD,UAAU,EAAE,kBAAkB;IAC9B,OAAO,EAAE,eAAe;IACxB,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF,gGAAgG;AAChG,MAAM,CAAC,MAAM,kBAAkB,GAA+B,gBAAgB,CAAC,GAAG,CAChF,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,CAC3C,CAAC;AAEF;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,UAAU,CAAU;IAE7B,YAAY,UAAmB;QAC7B,KAAK,CAAC,iDAAiD,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,KAAK,CAAC;IACtB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,gBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC9F,CAAC;AAQD,MAAM,UAAU,eAAe,CAAC,UAAmB;IACjD,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACvE,OAAO,cAAc,CAAC,UAAU,CAAC,CAAC;AACpC,CAAC","sourcesContent":["// The sport registry: the only way a sport's adapter is selected, always by `leagueType` (RULE-QS-SPORT-902).\n// A `LeagueType` member with no adapter does not compile (RULE-QS-SPORT-905), and a value outside the union\n// has no adapter — there is no default sport.\n\nimport { LEAGUE_TYPE_KEYS } from '../types/sports.js';\nimport type { LeagueType } from '../types/sports.js';\nimport { BASKETBALL_ADAPTER } from './basketball.js';\nimport { CURLING_ADAPTER } from './curling.js';\nimport { FOOTBALL_ADAPTER } from './football.js';\nimport type { AnySportAdapter, SportAdapterRegistry } from './sport-adapter.js';\n\nexport const SPORT_ADAPTERS: SportAdapterRegistry = {\n Basketball: BASKETBALL_ADAPTER,\n Curling: CURLING_ADAPTER,\n Football: FOOTBALL_ADAPTER,\n};\n\n/** Every adapter, in `LEAGUE_TYPE_KEYS` order: for every surface that lists or picks sports. */\nexport const SPORT_ADAPTER_LIST: readonly AnySportAdapter[] = LEAGUE_TYPE_KEYS.map(\n (leagueType) => SPORT_ADAPTERS[leagueType],\n);\n\n/**\n * A `leagueType` with no registered adapter: missing, `null`, a wrong case, or any value outside the\n * union. It is a defect, never a designed refusal — every stored league and game carries a valid\n * `leagueType` — so it surfaces as an unexpected fault.\n */\nexport class UnknownSportError extends Error {\n readonly leagueType: unknown;\n\n constructor(leagueType: unknown) {\n super(`No sport adapter is registered for leagueType ${describeValue(leagueType)}.`);\n this.name = 'UnknownSportError';\n this.leagueType = leagueType;\n }\n}\n\nfunction describeValue(value: unknown): string {\n if (typeof value === 'string') return JSON.stringify(value);\n try {\n return String(value);\n } catch {\n return typeof value;\n }\n}\n\nfunction isLeagueType(value: unknown): value is LeagueType {\n return typeof value === 'string' && (LEAGUE_TYPE_KEYS as readonly string[]).includes(value);\n}\n\n/**\n * The adapter of `leagueType` as read off a document. Throws `UnknownSportError` for any value\n * outside the union.\n */\nexport function getSportAdapter<L extends LeagueType>(leagueType: L): SportAdapterRegistry[L];\nexport function getSportAdapter(leagueType: unknown): AnySportAdapter;\nexport function getSportAdapter(leagueType: unknown): AnySportAdapter {\n if (!isLeagueType(leagueType)) throw new UnknownSportError(leagueType);\n return SPORT_ADAPTERS[leagueType];\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  // The replay: a game's rollup rebuilt from its event documents, in `sequence` order, from the
2
- // sport's empty stats (SPORT-101). It is the one fold every path that derives a game's numbers runs —
2
+ // sport's empty stats (RULE-QS-SPORT-911). It is the one fold every path that derives a game's numbers runs —
3
3
  // the Functions, the client's queue, and the browser's week view — and the store driver folds each
4
4
  // new event it accepts through the same step, `foldStatEvent`, so the replay and the write can never
5
5
  // disagree about what an event does.
@@ -1 +1 @@
1
- {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/sports/replay.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,sGAAsG;AACtG,mGAAmG;AACnG,qGAAqG;AACrG,qCAAqC;AAIrC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAsB,MAAM,cAAc,CAAC;AAGlF,qGAAqG;AACrG,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,OAAO,CAAS;IAEzB,YAAY,OAAe,EAAE,KAAc;QACzC,KAAK,CACH,cAAc,OAAO,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACrG,EAAE,KAAK,EAAE,CACV,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAwB,EACxB,KAAgB,EAChB,IAAiB;IAEjB,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACxF,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO;QACL,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE;QACrD,WAAW,EAAE,KAAK,CAAC,WAAW;KAClB,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAwB,EACxB,KAAgB,EAChB,KAAoB,EACpB,QAAwD;IAExD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC7C,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,CAAC,aAAa,aAAa,OAAO,CAAC,UAAU,yBAAyB,CACjG,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,eAAe,CAC7B,OAAwB,EACxB,IAAiB,EACjB,MAAgC;IAEhC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,IAAI,KAAK,GAAc,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtF,IAAI,CAAC;YACH,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QACjF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAClD,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["// The replay: a game's rollup rebuilt from its event documents, in `sequence` order, from the\n// sport's empty stats (SPORT-101). It is the one fold every path that derives a game's numbers runs —\n// the Functions, the client's queue, and the browser's week view — and the store driver folds each\n// new event it accepts through the same step, `foldStatEvent`, so the replay and the write can never\n// disagree about what an event does.\n\nimport type { GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport { isSportStatEventIn, isStatVoid, type StatEventBody } from './scoring.js';\nimport type { AnySportAdapter, GameTeamIds } from './sport-adapter.js';\n\n/** A stored event the replay could not fold — damage in the event store, never a designed answer. */\nexport class StatReplayError extends Error {\n readonly eventId: string;\n\n constructor(eventId: string, cause: unknown) {\n super(\n `Stat event ${eventId} cannot be replayed: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n this.name = 'StatReplayError';\n this.eventId = eventId;\n }\n}\n\n/**\n * `state` with a team line for each of the game's teams, the missing ones from the sport's empty\n * stats — so a team's first stat is never refused as a team not in the game. No other line is touched.\n */\nexport function withGameTeamLines(\n adapter: AnySportAdapter,\n state: GameStats,\n game: GameTeamIds,\n): GameStats {\n if (game.teamIds.every((teamId) => state.teamStats[teamId] !== undefined)) return state;\n const empty = adapter.emptyStats(game);\n return {\n teamStats: { ...empty.teamStats, ...state.teamStats },\n playerStats: state.playerStats,\n } as GameStats;\n}\n\n/**\n * `state` with one event folded in: a sport event or a correction applied, or a void's target\n * undone. `targetOf` answers from the events already folded; a void whose target is missing, a void,\n * or a correction throws.\n */\nexport function foldStatEvent(\n adapter: AnySportAdapter,\n state: GameStats,\n event: StatEventBody,\n targetOf: (eventId: string) => StatEventBody | undefined,\n): GameStats {\n if (isStatVoid(event)) {\n const target = targetOf(event.targetEventId);\n if (!isSportStatEventIn(adapter.eventTypes, target)) {\n throw new Error(\n `the void's target ${event.targetEventId} is not a ${adapter.leagueType} event folded before it`,\n );\n }\n return adapter.undo(state, target);\n }\n return adapter.apply(state, event);\n}\n\n/** The rollup a game's events replay to: every line, derived fields included, and each team's score. */\nexport function replayGameStats(\n adapter: AnySportAdapter,\n game: GameTeamIds,\n events: readonly GameStatEvent[],\n): GameStats {\n const folded = new Map<string, GameStatEvent>();\n let state: GameStats = adapter.emptyStats(game);\n for (const event of [...events].sort((left, right) => left.sequence - right.sequence)) {\n try {\n state = foldStatEvent(adapter, state, event, (eventId) => folded.get(eventId));\n } catch (error) {\n throw new StatReplayError(event.eventId, error);\n }\n folded.set(event.eventId, event);\n }\n return state;\n}\n"]}
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/sports/replay.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,8GAA8G;AAC9G,mGAAmG;AACnG,qGAAqG;AACrG,qCAAqC;AAIrC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAsB,MAAM,cAAc,CAAC;AAGlF,qGAAqG;AACrG,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,OAAO,CAAS;IAEzB,YAAY,OAAe,EAAE,KAAc;QACzC,KAAK,CACH,cAAc,OAAO,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACrG,EAAE,KAAK,EAAE,CACV,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAwB,EACxB,KAAgB,EAChB,IAAiB;IAEjB,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACxF,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO;QACL,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE;QACrD,WAAW,EAAE,KAAK,CAAC,WAAW;KAClB,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAwB,EACxB,KAAgB,EAChB,KAAoB,EACpB,QAAwD;IAExD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC7C,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,CAAC,aAAa,aAAa,OAAO,CAAC,UAAU,yBAAyB,CACjG,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,eAAe,CAC7B,OAAwB,EACxB,IAAiB,EACjB,MAAgC;IAEhC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,IAAI,KAAK,GAAc,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtF,IAAI,CAAC;YACH,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QACjF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAClD,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["// The replay: a game's rollup rebuilt from its event documents, in `sequence` order, from the\n// sport's empty stats (RULE-QS-SPORT-911). It is the one fold every path that derives a game's numbers runs —\n// the Functions, the client's queue, and the browser's week view — and the store driver folds each\n// new event it accepts through the same step, `foldStatEvent`, so the replay and the write can never\n// disagree about what an event does.\n\nimport type { GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport { isSportStatEventIn, isStatVoid, type StatEventBody } from './scoring.js';\nimport type { AnySportAdapter, GameTeamIds } from './sport-adapter.js';\n\n/** A stored event the replay could not fold — damage in the event store, never a designed answer. */\nexport class StatReplayError extends Error {\n readonly eventId: string;\n\n constructor(eventId: string, cause: unknown) {\n super(\n `Stat event ${eventId} cannot be replayed: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n this.name = 'StatReplayError';\n this.eventId = eventId;\n }\n}\n\n/**\n * `state` with a team line for each of the game's teams, the missing ones from the sport's empty\n * stats — so a team's first stat is never refused as a team not in the game. No other line is touched.\n */\nexport function withGameTeamLines(\n adapter: AnySportAdapter,\n state: GameStats,\n game: GameTeamIds,\n): GameStats {\n if (game.teamIds.every((teamId) => state.teamStats[teamId] !== undefined)) return state;\n const empty = adapter.emptyStats(game);\n return {\n teamStats: { ...empty.teamStats, ...state.teamStats },\n playerStats: state.playerStats,\n } as GameStats;\n}\n\n/**\n * `state` with one event folded in: a sport event or a correction applied, or a void's target\n * undone. `targetOf` answers from the events already folded; a void whose target is missing, a void,\n * or a correction throws.\n */\nexport function foldStatEvent(\n adapter: AnySportAdapter,\n state: GameStats,\n event: StatEventBody,\n targetOf: (eventId: string) => StatEventBody | undefined,\n): GameStats {\n if (isStatVoid(event)) {\n const target = targetOf(event.targetEventId);\n if (!isSportStatEventIn(adapter.eventTypes, target)) {\n throw new Error(\n `the void's target ${event.targetEventId} is not a ${adapter.leagueType} event folded before it`,\n );\n }\n return adapter.undo(state, target);\n }\n return adapter.apply(state, event);\n}\n\n/** The rollup a game's events replay to: every line, derived fields included, and each team's score. */\nexport function replayGameStats(\n adapter: AnySportAdapter,\n game: GameTeamIds,\n events: readonly GameStatEvent[],\n): GameStats {\n const folded = new Map<string, GameStatEvent>();\n let state: GameStats = adapter.emptyStats(game);\n for (const event of [...events].sort((left, right) => left.sequence - right.sequence)) {\n try {\n state = foldStatEvent(adapter, state, event, (eventId) => folded.get(eventId));\n } catch (error) {\n throw new StatReplayError(event.eventId, error);\n }\n folded.set(event.eventId, event);\n }\n return state;\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  // Whether a game's stored numbers are what produced them: the rollup's counters and the score mirror
2
2
  // against a replay — of the event documents for a live game, of the finalized record for a completed
3
3
  // one. Finalize, reopen, a ghost link, and validation ask this one comparison, so a hand edit to the
4
- // game document is found the same way everywhere and never adopted or discarded silently (ENG-009).
4
+ // game document is found the same way everywhere and never adopted or discarded silently (RULE-STD-ENG-009).
5
5
  //
6
6
  // Counters are compared, never a derived field: a stored FP a weight change left stale is not a
7
7
  // difference in what was recorded, and the writer re-derives it.
@@ -1 +1 @@
1
- {"version":3,"file":"rollup-check.js","sourceRoot":"","sources":["../../src/sports/rollup-check.ts"],"names":[],"mappings":"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","sourcesContent":["// Whether a game's stored numbers are what produced them: the rollup's counters and the score mirror\n// against a replay — of the event documents for a live game, of the finalized record for a completed\n// one. Finalize, reopen, a ghost link, and validation ask this one comparison, so a hand edit to the\n// game document is found the same way everywhere and never adopted or discarded silently (ENG-009).\n//\n// Counters are compared, never a derived field: a stored FP a weight change left stale is not a\n// difference in what was recorded, and the writer re-derives it.\n\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport { gameScoreMirror } from './score-mirror.js';\nimport { isAllZeroLine } from './scoring.js';\nimport type { StatLineValues } from './scoring.js';\nimport type { AnySportAdapter } from './sport-adapter.js';\n\n/** One place a stored rollup or score mirror differs from the replay. */\nexport interface RollupDifference {\n readonly kind: 'rollupMismatch' | 'scoreMirrorMismatch';\n /** The team id of a team line or score, or the roster-entry id of a player line. */\n readonly key: string;\n readonly field: string;\n readonly stored: unknown;\n readonly replayed: unknown;\n}\n\n/** Two stored line values alike: a number, or a list of round slots, slot by slot. */\nfunction sameValue(left: unknown, right: unknown): boolean {\n if (Array.isArray(left) && Array.isArray(right)) {\n return (\n left.length === right.length && left.every((slot, index) => Object.is(slot, right[index]))\n );\n }\n return Object.is(left, right);\n}\n\n/** The fields a line is compared on: every field of a team line, the counters of a player line. */\nfunction compared(\n line: StatLineValues | undefined,\n fields: readonly string[] | null,\n): Record<string, unknown> {\n if (line === undefined) return {};\n const keys = fields ?? Object.keys(line);\n return Object.fromEntries(\n keys.filter((field) => line[field] !== undefined).map((field) => [field, line[field]]),\n );\n}\n\n/**\n * Every difference between the stored rollup and score mirror and the replay. A player line that\n * holds nothing and one that is absent are alike; a team line is compared whole, its round slots\n * included, for each of the game's teams.\n */\nexport function rollupDifferences(\n adapter: AnySportAdapter,\n game: {\n readonly teamIds: readonly string[];\n readonly stats: GameStats;\n readonly score: Readonly<Record<string, number>>;\n },\n replayed: GameStats,\n): RollupDifference[] {\n const differences: RollupDifference[] = [];\n const storedTeams = game.stats.teamStats as Readonly<Record<string, StatLineValues>>;\n const replayedTeams = replayed.teamStats as Readonly<Record<string, StatLineValues>>;\n const teamKeys = new Set([...game.teamIds, ...Object.keys(storedTeams)]);\n for (const teamId of teamKeys) {\n const stored = compared(storedTeams[teamId], null);\n const replay = compared(replayedTeams[teamId], null);\n for (const field of new Set([...Object.keys(stored), ...Object.keys(replay)])) {\n if (!sameValue(stored[field], replay[field])) {\n differences.push({\n kind: 'rollupMismatch',\n key: teamId,\n field,\n stored: stored[field],\n replayed: replay[field],\n });\n }\n }\n }\n\n const counters = adapter.playerStatKeys as readonly string[];\n const storedPlayers = game.stats.playerStats as Readonly<Record<string, StatLineValues>>;\n const replayedPlayers = replayed.playerStats as Readonly<Record<string, StatLineValues>>;\n const holding = (lines: Readonly<Record<string, StatLineValues>>) =>\n Object.keys(lines).filter(\n (entryId) => !isAllZeroLine(lines[entryId] as StatLineValues, counters),\n );\n for (const entryId of new Set([...holding(storedPlayers), ...holding(replayedPlayers)])) {\n const stored = compared(storedPlayers[entryId], counters);\n const replay = compared(replayedPlayers[entryId], counters);\n for (const field of counters) {\n const [left, right] = [stored[field] ?? 0, replay[field] ?? 0];\n if (left !== right)\n differences.push({\n kind: 'rollupMismatch',\n key: entryId,\n field,\n stored: left,\n replayed: right,\n });\n }\n }\n\n const mirror = gameScoreMirror(replayed as { teamStats: Record<string, { score: number }> });\n for (const teamId of new Set([...Object.keys(game.score), ...Object.keys(mirror)])) {\n if (game.score[teamId] !== mirror[teamId]) {\n differences.push({\n kind: 'scoreMirrorMismatch',\n key: teamId,\n field: 'score',\n stored: game.score[teamId],\n replayed: mirror[teamId],\n });\n }\n }\n return differences;\n}\n"]}
1
+ {"version":3,"file":"rollup-check.js","sourceRoot":"","sources":["../../src/sports/rollup-check.ts"],"names":[],"mappings":"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","sourcesContent":["// Whether a game's stored numbers are what produced them: the rollup's counters and the score mirror\n// against a replay — of the event documents for a live game, of the finalized record for a completed\n// one. Finalize, reopen, a ghost link, and validation ask this one comparison, so a hand edit to the\n// game document is found the same way everywhere and never adopted or discarded silently (RULE-STD-ENG-009).\n//\n// Counters are compared, never a derived field: a stored FP a weight change left stale is not a\n// difference in what was recorded, and the writer re-derives it.\n\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport { gameScoreMirror } from './score-mirror.js';\nimport { isAllZeroLine } from './scoring.js';\nimport type { StatLineValues } from './scoring.js';\nimport type { AnySportAdapter } from './sport-adapter.js';\n\n/** One place a stored rollup or score mirror differs from the replay. */\nexport interface RollupDifference {\n readonly kind: 'rollupMismatch' | 'scoreMirrorMismatch';\n /** The team id of a team line or score, or the roster-entry id of a player line. */\n readonly key: string;\n readonly field: string;\n readonly stored: unknown;\n readonly replayed: unknown;\n}\n\n/** Two stored line values alike: a number, or a list of round slots, slot by slot. */\nfunction sameValue(left: unknown, right: unknown): boolean {\n if (Array.isArray(left) && Array.isArray(right)) {\n return (\n left.length === right.length && left.every((slot, index) => Object.is(slot, right[index]))\n );\n }\n return Object.is(left, right);\n}\n\n/** The fields a line is compared on: every field of a team line, the counters of a player line. */\nfunction compared(\n line: StatLineValues | undefined,\n fields: readonly string[] | null,\n): Record<string, unknown> {\n if (line === undefined) return {};\n const keys = fields ?? Object.keys(line);\n return Object.fromEntries(\n keys.filter((field) => line[field] !== undefined).map((field) => [field, line[field]]),\n );\n}\n\n/**\n * Every difference between the stored rollup and score mirror and the replay. A player line that\n * holds nothing and one that is absent are alike; a team line is compared whole, its round slots\n * included, for each of the game's teams.\n */\nexport function rollupDifferences(\n adapter: AnySportAdapter,\n game: {\n readonly teamIds: readonly string[];\n readonly stats: GameStats;\n readonly score: Readonly<Record<string, number>>;\n },\n replayed: GameStats,\n): RollupDifference[] {\n const differences: RollupDifference[] = [];\n const storedTeams = game.stats.teamStats as Readonly<Record<string, StatLineValues>>;\n const replayedTeams = replayed.teamStats as Readonly<Record<string, StatLineValues>>;\n const teamKeys = new Set([...game.teamIds, ...Object.keys(storedTeams)]);\n for (const teamId of teamKeys) {\n const stored = compared(storedTeams[teamId], null);\n const replay = compared(replayedTeams[teamId], null);\n for (const field of new Set([...Object.keys(stored), ...Object.keys(replay)])) {\n if (!sameValue(stored[field], replay[field])) {\n differences.push({\n kind: 'rollupMismatch',\n key: teamId,\n field,\n stored: stored[field],\n replayed: replay[field],\n });\n }\n }\n }\n\n const counters = adapter.playerStatKeys as readonly string[];\n const storedPlayers = game.stats.playerStats as Readonly<Record<string, StatLineValues>>;\n const replayedPlayers = replayed.playerStats as Readonly<Record<string, StatLineValues>>;\n const holding = (lines: Readonly<Record<string, StatLineValues>>) =>\n Object.keys(lines).filter(\n (entryId) => !isAllZeroLine(lines[entryId] as StatLineValues, counters),\n );\n for (const entryId of new Set([...holding(storedPlayers), ...holding(replayedPlayers)])) {\n const stored = compared(storedPlayers[entryId], counters);\n const replay = compared(replayedPlayers[entryId], counters);\n for (const field of counters) {\n const [left, right] = [stored[field] ?? 0, replay[field] ?? 0];\n if (left !== right)\n differences.push({\n kind: 'rollupMismatch',\n key: entryId,\n field,\n stored: left,\n replayed: right,\n });\n }\n }\n\n const mirror = gameScoreMirror(replayed as { teamStats: Record<string, { score: number }> });\n for (const teamId of new Set([...Object.keys(game.score), ...Object.keys(mirror)])) {\n if (game.score[teamId] !== mirror[teamId]) {\n differences.push({\n kind: 'scoreMirrorMismatch',\n key: teamId,\n field: 'score',\n stored: game.score[teamId],\n replayed: mirror[teamId],\n });\n }\n }\n return differences;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  // A game's `score`: each team's score, mirrored from its `stats.teamStats` line so a schedule or list
2
2
  // reads the result without the stats. Every writer of a team line writes the mirror from the line it
3
- // wrote, in the same write, through this one derivation (SPORT-107).
3
+ // wrote, in the same write, through this one derivation (RULE-QS-SPORT-917).
4
4
  /** Every sport's team line carries its `score`; the mirror is keyed by the same team ids. */
5
5
  export function gameScoreMirror(stats) {
6
6
  return Object.fromEntries(Object.entries(stats.teamStats).map(([teamId, line]) => [teamId, line.score]));
@@ -1 +1 @@
1
- {"version":3,"file":"score-mirror.js","sourceRoot":"","sources":["../../src/sports/score-mirror.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,qGAAqG;AACrG,qEAAqE;AAErE,6FAA6F;AAC7F,MAAM,UAAU,eAAe,CAAC,KAE/B;IACC,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAC9E,CAAC;AACJ,CAAC","sourcesContent":["// A game's `score`: each team's score, mirrored from its `stats.teamStats` line so a schedule or list\n// reads the result without the stats. Every writer of a team line writes the mirror from the line it\n// wrote, in the same write, through this one derivation (SPORT-107).\n\n/** Every sport's team line carries its `score`; the mirror is keyed by the same team ids. */\nexport function gameScoreMirror(stats: {\n readonly teamStats: Readonly<Record<string, { readonly score: number }>>;\n}): Record<string, number> {\n return Object.fromEntries(\n Object.entries(stats.teamStats).map(([teamId, line]) => [teamId, line.score]),\n );\n}\n"]}
1
+ {"version":3,"file":"score-mirror.js","sourceRoot":"","sources":["../../src/sports/score-mirror.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,qGAAqG;AACrG,6EAA6E;AAE7E,6FAA6F;AAC7F,MAAM,UAAU,eAAe,CAAC,KAE/B;IACC,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAC9E,CAAC;AACJ,CAAC","sourcesContent":["// A game's `score`: each team's score, mirrored from its `stats.teamStats` line so a schedule or list\n// reads the result without the stats. Every writer of a team line writes the mirror from the line it\n// wrote, in the same write, through this one derivation (RULE-QS-SPORT-917).\n\n/** Every sport's team line carries its `score`; the mirror is keyed by the same team ids. */\nexport function gameScoreMirror(stats: {\n readonly teamStats: Readonly<Record<string, { readonly score: number }>>;\n}): Record<string, number> {\n return Object.fromEntries(\n Object.entries(stats.teamStats).map(([teamId, line]) => [teamId, line.score]),\n );\n}\n"]}
@@ -3,7 +3,7 @@ import type { StatCounterKey } from '../types/sports.js';
3
3
  import type { ScoringPanelKind } from '../types/stats.js';
4
4
  import type { GamePlayerLine, GameTeamLine } from './scoring.js';
5
5
  /**
6
- * Names read at render time (ARCH-103, SPORT-104): a person by user id — a removed ghost resolving to
6
+ * Names read at render time (RULE-STD-ARCH-103, RULE-QS-SPORT-914): a person by user id — a removed ghost resolving to
7
7
  * "removed player" — and a team by team id. An event's stored name snapshot is never one of them.
8
8
  */
9
9
  export interface StatEventNameResolver {
@@ -1,6 +1,6 @@
1
1
  // What the scoring screen, the scoreboard, and the play-by-play render for a sport, declared by its
2
- // adapter so no shared surface branches on a sport (SPORT-004, SPORT-106). Every label lives here or
3
- // in the adapter, never in a component (ENG-005).
2
+ // adapter so no shared surface branches on a sport (RULE-QS-SPORT-904, RULE-QS-SPORT-916). Every label lives here or
3
+ // in the adapter, never in a component (RULE-STD-ENG-005).
4
4
  import { isStatCorrection, isStatVoid } from './scoring.js';
5
5
  /** A signed change as a play shows it: `+3`, `-2`. */
6
6
  export function formatSignedValue(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"scoring-display.js","sourceRoot":"","sources":["../../src/sports/scoring-display.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,qGAAqG;AACrG,kDAAkD;AAMlD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAkF5D,sDAAsD;AACtD,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAE/D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAoB,EACpB,OAA8B,EAC9B,WAGC;IAED,IAAI,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,qBAAqB,CAAC;IACpD,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC;IACxF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC;IAC/C,MAAM,IAAI,GACR,KAAK,CAAC,YAAY,KAAK,SAAS;QAC9B,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAC5B,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;IAC9E,OAAO,GAAG,KAAK,iBAAiB,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC/E,CAAC","sourcesContent":["// What the scoring screen, the scoreboard, and the play-by-play render for a sport, declared by its\n// adapter so no shared surface branches on a sport (SPORT-004, SPORT-106). Every label lives here or\n// in the adapter, never in a component (ENG-005).\n\nimport type { GameStatEvent, GameStatEventType } from '../doc-schemas/game-stats.js';\nimport type { StatCounterKey } from '../types/sports.js';\nimport type { ScoringPanelKind } from '../types/stats.js';\nimport type { GamePlayerLine, GameTeamLine } from './scoring.js';\nimport { isStatCorrection, isStatVoid } from './scoring.js';\n\n/**\n * Names read at render time (ARCH-103, SPORT-104): a person by user id — a removed ghost resolving to\n * \"removed player\" — and a team by team id. An event's stored name snapshot is never one of them.\n */\nexport interface StatEventNameResolver {\n readonly player: (userId: string) => string;\n readonly team: (teamId: string) => string;\n}\n\n/** An event type's button label on the scoring panel and its label in the play-by-play. */\nexport interface StatEventLabel {\n readonly button: string;\n readonly play: string;\n}\n\n/** Each field of a team line and of a player line, by label — what a correction row names. */\nexport interface StatFieldLabels<\n TeamField extends StatCounterKey,\n PlayerField extends StatCounterKey,\n> {\n readonly team: Readonly<Record<TeamField, string>>;\n readonly player: Readonly<Record<PlayerField, string>>;\n}\n\n/** One figure a team card shows: from the team's game line and the player lines keyed to its side. */\nexport interface ScoreboardFigure {\n readonly id: string;\n readonly label: string;\n readonly value: (teamLine: GameTeamLine, sideLines: readonly GamePlayerLine[]) => number;\n}\n\n/**\n * The one scoreboard every game screen renders: each team's figures, the jump-ball marker where the\n * sport has one, and — for a sport played in rounds — each team's round-by-round grid.\n */\nexport interface Scoreboard {\n readonly figures: readonly ScoreboardFigure[];\n /**\n * Whether the sport has a jump-ball possession marker: the team cards show it, and the game's\n * scorers set it through its own writer, never a stat event.\n */\n readonly jumpBall: boolean;\n /**\n * Present only for a sport played in rounds: a team line's slot per round, `null` where the round\n * is not recorded, so the grid tells an unplayed round from a scoreless one.\n */\n readonly roundSlots?: (teamLine: GameTeamLine) => readonly (number | null)[];\n}\n\n/** A sport played in rounds: how many, and what one is called (\"Round n / 14\"). */\nexport interface RoundsDisplay {\n readonly count: number;\n readonly label: string;\n}\n\n/** Pick a present player, then tap one of their events; the team row carries the team's own. */\nexport interface PlayerEventsScoringPanel {\n readonly kind: Extract<ScoringPanelKind, 'playerEvents'>;\n readonly playerEventTypes: readonly GameStatEventType[];\n /** The events that open the assist picker: the team's other present players, and \"No assist\". */\n readonly assistableEventTypes: readonly GameStatEventType[];\n readonly teamEventTypes: readonly GameStatEventType[];\n}\n\n/** One button per event for each team. */\nexport interface TeamPointsScoringPanel {\n readonly kind: Extract<ScoringPanelKind, 'teamPoints'>;\n readonly eventTypes: readonly GameStatEventType[];\n}\n\n/** Each team's +1 through +`maxPoints` for the next round, and one button for a blank round. */\nexport interface RoundsScoringPanel {\n readonly kind: Extract<ScoringPanelKind, 'rounds'>;\n readonly scoreEventType: GameStatEventType;\n readonly blankEventType: GameStatEventType;\n readonly maxPoints: number;\n}\n\nexport type ScoringPanel = PlayerEventsScoringPanel | TeamPointsScoringPanel | RoundsScoringPanel;\n\n/** A signed change as a play shows it: `+3`, `-2`. */\nexport function formatSignedValue(value: number): string {\n return value > 0 ? `+${value}` : String(value);\n}\n\nexport const VOID_PLAY_DESCRIPTION = 'Undo of an earlier play';\n\n/**\n * The play-by-play line of a store-level event, the same for every sport: a correction names the\n * field it moved through the sport's field labels, and never reads as a play. `null` for a sport's\n * own event.\n */\nexport function describeStoreEvent(\n event: GameStatEvent,\n resolve: StatEventNameResolver,\n fieldLabels: {\n readonly team: Partial<Record<StatCounterKey, string>>;\n readonly player: Partial<Record<StatCounterKey, string>>;\n },\n): string | null {\n if (isStatVoid(event)) return VOID_PLAY_DESCRIPTION;\n if (!isStatCorrection(event)) return null;\n const labels = event.playerUserId === undefined ? fieldLabels.team : fieldLabels.player;\n const field = labels[event.stat] ?? event.stat;\n const line =\n event.playerUserId === undefined\n ? resolve.team(event.teamId)\n : `${resolve.player(event.playerUserId)} (${resolve.team(event.teamId)})`;\n return `${field} corrected by ${formatSignedValue(event.value)} for ${line}`;\n}\n"]}
1
+ {"version":3,"file":"scoring-display.js","sourceRoot":"","sources":["../../src/sports/scoring-display.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,qHAAqH;AACrH,2DAA2D;AAM3D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAkF5D,sDAAsD;AACtD,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAE/D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAoB,EACpB,OAA8B,EAC9B,WAGC;IAED,IAAI,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,qBAAqB,CAAC;IACpD,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC;IACxF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC;IAC/C,MAAM,IAAI,GACR,KAAK,CAAC,YAAY,KAAK,SAAS;QAC9B,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAC5B,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;IAC9E,OAAO,GAAG,KAAK,iBAAiB,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC/E,CAAC","sourcesContent":["// What the scoring screen, the scoreboard, and the play-by-play render for a sport, declared by its\n// adapter so no shared surface branches on a sport (RULE-QS-SPORT-904, RULE-QS-SPORT-916). Every label lives here or\n// in the adapter, never in a component (RULE-STD-ENG-005).\n\nimport type { GameStatEvent, GameStatEventType } from '../doc-schemas/game-stats.js';\nimport type { StatCounterKey } from '../types/sports.js';\nimport type { ScoringPanelKind } from '../types/stats.js';\nimport type { GamePlayerLine, GameTeamLine } from './scoring.js';\nimport { isStatCorrection, isStatVoid } from './scoring.js';\n\n/**\n * Names read at render time (RULE-STD-ARCH-103, RULE-QS-SPORT-914): a person by user id — a removed ghost resolving to\n * \"removed player\" — and a team by team id. An event's stored name snapshot is never one of them.\n */\nexport interface StatEventNameResolver {\n readonly player: (userId: string) => string;\n readonly team: (teamId: string) => string;\n}\n\n/** An event type's button label on the scoring panel and its label in the play-by-play. */\nexport interface StatEventLabel {\n readonly button: string;\n readonly play: string;\n}\n\n/** Each field of a team line and of a player line, by label — what a correction row names. */\nexport interface StatFieldLabels<\n TeamField extends StatCounterKey,\n PlayerField extends StatCounterKey,\n> {\n readonly team: Readonly<Record<TeamField, string>>;\n readonly player: Readonly<Record<PlayerField, string>>;\n}\n\n/** One figure a team card shows: from the team's game line and the player lines keyed to its side. */\nexport interface ScoreboardFigure {\n readonly id: string;\n readonly label: string;\n readonly value: (teamLine: GameTeamLine, sideLines: readonly GamePlayerLine[]) => number;\n}\n\n/**\n * The one scoreboard every game screen renders: each team's figures, the jump-ball marker where the\n * sport has one, and — for a sport played in rounds — each team's round-by-round grid.\n */\nexport interface Scoreboard {\n readonly figures: readonly ScoreboardFigure[];\n /**\n * Whether the sport has a jump-ball possession marker: the team cards show it, and the game's\n * scorers set it through its own writer, never a stat event.\n */\n readonly jumpBall: boolean;\n /**\n * Present only for a sport played in rounds: a team line's slot per round, `null` where the round\n * is not recorded, so the grid tells an unplayed round from a scoreless one.\n */\n readonly roundSlots?: (teamLine: GameTeamLine) => readonly (number | null)[];\n}\n\n/** A sport played in rounds: how many, and what one is called (\"Round n / 14\"). */\nexport interface RoundsDisplay {\n readonly count: number;\n readonly label: string;\n}\n\n/** Pick a present player, then tap one of their events; the team row carries the team's own. */\nexport interface PlayerEventsScoringPanel {\n readonly kind: Extract<ScoringPanelKind, 'playerEvents'>;\n readonly playerEventTypes: readonly GameStatEventType[];\n /** The events that open the assist picker: the team's other present players, and \"No assist\". */\n readonly assistableEventTypes: readonly GameStatEventType[];\n readonly teamEventTypes: readonly GameStatEventType[];\n}\n\n/** One button per event for each team. */\nexport interface TeamPointsScoringPanel {\n readonly kind: Extract<ScoringPanelKind, 'teamPoints'>;\n readonly eventTypes: readonly GameStatEventType[];\n}\n\n/** Each team's +1 through +`maxPoints` for the next round, and one button for a blank round. */\nexport interface RoundsScoringPanel {\n readonly kind: Extract<ScoringPanelKind, 'rounds'>;\n readonly scoreEventType: GameStatEventType;\n readonly blankEventType: GameStatEventType;\n readonly maxPoints: number;\n}\n\nexport type ScoringPanel = PlayerEventsScoringPanel | TeamPointsScoringPanel | RoundsScoringPanel;\n\n/** A signed change as a play shows it: `+3`, `-2`. */\nexport function formatSignedValue(value: number): string {\n return value > 0 ? `+${value}` : String(value);\n}\n\nexport const VOID_PLAY_DESCRIPTION = 'Undo of an earlier play';\n\n/**\n * The play-by-play line of a store-level event, the same for every sport: a correction names the\n * field it moved through the sport's field labels, and never reads as a play. `null` for a sport's\n * own event.\n */\nexport function describeStoreEvent(\n event: GameStatEvent,\n resolve: StatEventNameResolver,\n fieldLabels: {\n readonly team: Partial<Record<StatCounterKey, string>>;\n readonly player: Partial<Record<StatCounterKey, string>>;\n },\n): string | null {\n if (isStatVoid(event)) return VOID_PLAY_DESCRIPTION;\n if (!isStatCorrection(event)) return null;\n const labels = event.playerUserId === undefined ? fieldLabels.team : fieldLabels.player;\n const field = labels[event.stat] ?? event.stat;\n const line =\n event.playerUserId === undefined\n ? resolve.team(event.teamId)\n : `${resolve.player(event.playerUserId)} (${resolve.team(event.teamId)})`;\n return `${field} corrected by ${formatSignedValue(event.value)} for ${line}`;\n}\n"]}
@@ -69,26 +69,26 @@ export interface SportScoring {
69
69
  readonly validate: (state: GameStats, event: StatEventBody, roster: StatEventRoster) => StatEventVerdict;
70
70
  /**
71
71
  * `state` with one sport event or one `STAT_CORRECTION` applied. A missing line is created from
72
- * the sport's empty line (SPORT-105); a player line whose counters all return to zero is dropped;
72
+ * the sport's empty line (RULE-QS-SPORT-915); a player line whose counters all return to zero is dropped;
73
73
  * a team line never is. Throws on a void, another sport's event, or a correction to a field not in
74
74
  * `correctableFields`.
75
75
  */
76
76
  readonly apply: (state: GameStats, event: StatEventBody) => GameStats;
77
- /** The exact inverse of `apply(target)` for one of the sport's own events (SPORT-103). */
77
+ /** The exact inverse of `apply(target)` for one of the sport's own events (RULE-QS-SPORT-913). */
78
78
  readonly undo: (state: GameStats, target: StatEventBody) => GameStats;
79
79
  /** `line` with every derived field of its kind; the only caller of any derivation formula. */
80
80
  readonly deriveLine: (kind: StatLineKind, line: StatLineValues) => StatLineValues;
81
81
  readonly correctableFields: CorrectableFields;
82
82
  /** The next round to record for a sport played in rounds, `null` once all are or for any other sport. */
83
83
  readonly nextRound: (state: GameStats) => number | null;
84
- /** Whether the game may be finalized (SPORT-202). */
84
+ /** Whether the game may be finalized (RULE-QS-SPORT-922). */
85
85
  readonly finalizationReadiness: (state: GameStats, game: GameTeamIds) => FinalizationReadiness;
86
86
  /**
87
87
  * The games-played decision finalize stores: the roster entries credited no game played, from
88
- * each entry's own line and absence, in roster order (SPORT-204).
88
+ * each entry's own line and absence, in roster order (RULE-QS-SPORT-924).
89
89
  */
90
90
  readonly participation: (state: GameStats, rosterPlayerIds: readonly string[], absentRosterPlayerIds: readonly string[]) => string[];
91
- /** What a completed game adds to every derived destination (SPORT-207). */
91
+ /** What a completed game adds to every derived destination (RULE-QS-SPORT-927). */
92
92
  readonly contribution: (game: FinalizedGame) => GameContribution;
93
93
  }
94
94
  /** Whether every counter of a line is zero — a player line the rollup then leaves out. */
@@ -1 +1 @@
1
- {"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/sports/scoring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/sports/scoring.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  // The scoring half of the sport adapter contract: the pure members the store driver, the replay, the
2
- // client's optimistic queue, and the aggregate cores call (SPORT-003). None issues a Firestore
2
+ // client's optimistic queue, and the aggregate cores call (RULE-QS-SPORT-903). None issues a Firestore
3
3
  // operation or an increment, reads a clock, or mints an id.
4
4
  //
5
5
  // Every member is typed over every sport's shapes, so any registered adapter is called the same way.
6
6
  // The registry chose the adapter by the game's `leagueType`, which is what makes the rollup it is
7
- // handed that sport's (SPORT-002); an event of another sport is refused by `validate` and thrown on
7
+ // handed that sport's (RULE-QS-SPORT-902); an event of another sport is refused by `validate` and thrown on
8
8
  // by `apply` and `undo`, never guessed at.
9
9
  import { STAT_CORRECTION_EVENT_TYPE, STAT_VOID_EVENT_TYPE } from '../types/stats.js';
10
10
  export function isStatVoid(event) {
@@ -1 +1 @@
1
- {"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/sports/scoring.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,+FAA+F;AAC/F,4DAA4D;AAC5D,EAAE;AACF,qGAAqG;AACrG,kGAAkG;AAClG,oGAAoG;AACpG,2CAA2C;AAK3C,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AA8BrF,MAAM,UAAU,UAAU,CAAC,KAAoB;IAC7C,OAAO,KAAK,CAAC,IAAI,KAAK,oBAAoB,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAoB;IACnD,OAAO,KAAK,CAAC,IAAI,KAAK,0BAA0B,CAAC;AACnD,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,kBAAkB,CAChC,UAA6B,EAC7B,KAAgC;IAEhC,OAAO,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAChE,CAAC;AAqBD,MAAM,CAAC,MAAM,mBAAmB,GAAqB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AAElE,MAAM,UAAU,eAAe,CAAC,IAA0B;IACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC;AAKD,MAAM,CAAC,MAAM,kBAAkB,GAA0B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAiDzE,0FAA0F;AAC1F,MAAM,UAAU,aAAa,CAAC,IAAoB,EAAE,WAA8B;IAChF,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC;AAChF,CAAC","sourcesContent":["// The scoring half of the sport adapter contract: the pure members the store driver, the replay, the\n// client's optimistic queue, and the aggregate cores call (SPORT-003). None issues a Firestore\n// operation or an increment, reads a clock, or mints an id.\n//\n// Every member is typed over every sport's shapes, so any registered adapter is called the same way.\n// The registry chose the adapter by the game's `leagueType`, which is what makes the rollup it is\n// handed that sport's (SPORT-002); an event of another sport is refused by `validate` and thrown on\n// by `apply` and `undo`, never guessed at.\n\nimport type { GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport type { StatCounterKey } from '../types/sports.js';\nimport { STAT_CORRECTION_EVENT_TYPE, STAT_VOID_EVENT_TYPE } from '../types/stats.js';\nimport type {\n FinalizationNotReadyReason,\n StatEventRecorderField,\n StatEventRefusalCode,\n StatEventSnapshotField,\n StatLineKind,\n} from '../types/stats.js';\nimport type { FinalizedGame, GameContribution } from './contribution.js';\nimport type { GameTeamIds } from './sport-adapter.js';\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\n/**\n * What an event says happened — its type and its payload. Its id, its `recordedAt`, and what the\n * writing core stamps on it are not part of it, so a stored event, a queued one, and a drafted one\n * are all bodies.\n */\nexport type StatEventBody = DistributiveOmit<\n GameStatEvent,\n 'eventId' | 'recordedAt' | StatEventRecorderField | StatEventSnapshotField\n>;\nexport type StatVoidBody = Extract<StatEventBody, { type: typeof STAT_VOID_EVENT_TYPE }>;\nexport type StatCorrectionBody = Extract<\n StatEventBody,\n { type: typeof STAT_CORRECTION_EVENT_TYPE }\n>;\n/** One of a sport's own events, as opposed to the store-level void and correction. */\nexport type SportStatEventBody = Exclude<StatEventBody, StatVoidBody | StatCorrectionBody>;\n\nexport function isStatVoid(event: StatEventBody): event is StatVoidBody {\n return event.type === STAT_VOID_EVENT_TYPE;\n}\n\nexport function isStatCorrection(event: StatEventBody): event is StatCorrectionBody {\n return event.type === STAT_CORRECTION_EVENT_TYPE;\n}\n\n/** Whether `event` is one of the sport events `eventTypes` declares — never a void or a correction. */\nexport function isSportStatEventIn(\n eventTypes: readonly string[],\n event: StatEventBody | undefined,\n): event is SportStatEventBody {\n return event !== undefined && eventTypes.includes(event.type);\n}\n\n/** One team's line in a game's rollup, whichever sport it counts. */\nexport type GameTeamLine = GameStats['teamStats'][string];\n/** One roster entry's line in a game's rollup, whichever sport it counts. */\nexport type GamePlayerLine = GameStats['playerStats'][string];\n\n/** A stat line of any kind as `deriveLine` reads and returns it: its counters, derived fields, and ends. */\nexport type StatLineValues = { readonly [field: string]: number | readonly (number | null)[] };\n\n/** The game an event is validated against: its teams, and the roster entries the batch names. */\nexport interface StatEventRoster {\n /** A team of the game is one of these — never inferred from which team lines a rollup holds. */\n readonly teamIds: readonly string[];\n /** The roster entries the driver read for the batch — never the whole roster. */\n readonly rosterPlayerIds: ReadonlySet<string>;\n}\n\nexport type StatEventVerdict =\n { readonly ok: true } | { readonly ok: false; readonly code: StatEventRefusalCode };\n\nexport const STAT_EVENT_ACCEPTED: StatEventVerdict = { ok: true };\n\nexport function refuseStatEvent(code: StatEventRefusalCode): StatEventVerdict {\n return { ok: false, code };\n}\n\nexport type FinalizationReadiness =\n { readonly ready: true } | { readonly ready: false; readonly reason: FinalizationNotReadyReason };\n\nexport const FINALIZATION_READY: FinalizationReadiness = { ready: true };\n\n/** The fields a `STAT_CORRECTION` may change, on a team line and on a player line. */\nexport interface CorrectableFields {\n readonly team: readonly StatCounterKey[];\n readonly player: readonly StatCounterKey[];\n}\n\nexport interface SportScoring {\n /**\n * Whether a new event may be recorded against `state`: its type is the sport's, its team is one\n * of the game's, every person it names is a roster entry of that team, and its values are a real\n * result. Only the store driver calls it, and only for a new event — a replay never re-validates\n * what was already accepted.\n */\n readonly validate: (\n state: GameStats,\n event: StatEventBody,\n roster: StatEventRoster,\n ) => StatEventVerdict;\n /**\n * `state` with one sport event or one `STAT_CORRECTION` applied. A missing line is created from\n * the sport's empty line (SPORT-105); a player line whose counters all return to zero is dropped;\n * a team line never is. Throws on a void, another sport's event, or a correction to a field not in\n * `correctableFields`.\n */\n readonly apply: (state: GameStats, event: StatEventBody) => GameStats;\n /** The exact inverse of `apply(target)` for one of the sport's own events (SPORT-103). */\n readonly undo: (state: GameStats, target: StatEventBody) => GameStats;\n /** `line` with every derived field of its kind; the only caller of any derivation formula. */\n readonly deriveLine: (kind: StatLineKind, line: StatLineValues) => StatLineValues;\n readonly correctableFields: CorrectableFields;\n /** The next round to record for a sport played in rounds, `null` once all are or for any other sport. */\n readonly nextRound: (state: GameStats) => number | null;\n /** Whether the game may be finalized (SPORT-202). */\n readonly finalizationReadiness: (state: GameStats, game: GameTeamIds) => FinalizationReadiness;\n /**\n * The games-played decision finalize stores: the roster entries credited no game played, from\n * each entry's own line and absence, in roster order (SPORT-204).\n */\n readonly participation: (\n state: GameStats,\n rosterPlayerIds: readonly string[],\n absentRosterPlayerIds: readonly string[],\n ) => string[];\n /** What a completed game adds to every derived destination (SPORT-207). */\n readonly contribution: (game: FinalizedGame) => GameContribution;\n}\n\n/** Whether every counter of a line is zero — a player line the rollup then leaves out. */\nexport function isAllZeroLine(line: StatLineValues, counterKeys: readonly string[]): boolean {\n return counterKeys.every((key) => line[key] === 0 || line[key] === undefined);\n}\n"]}
1
+ {"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/sports/scoring.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,uGAAuG;AACvG,4DAA4D;AAC5D,EAAE;AACF,qGAAqG;AACrG,kGAAkG;AAClG,4GAA4G;AAC5G,2CAA2C;AAK3C,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AA8BrF,MAAM,UAAU,UAAU,CAAC,KAAoB;IAC7C,OAAO,KAAK,CAAC,IAAI,KAAK,oBAAoB,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAoB;IACnD,OAAO,KAAK,CAAC,IAAI,KAAK,0BAA0B,CAAC;AACnD,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,kBAAkB,CAChC,UAA6B,EAC7B,KAAgC;IAEhC,OAAO,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAChE,CAAC;AAqBD,MAAM,CAAC,MAAM,mBAAmB,GAAqB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AAElE,MAAM,UAAU,eAAe,CAAC,IAA0B;IACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC;AAKD,MAAM,CAAC,MAAM,kBAAkB,GAA0B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAiDzE,0FAA0F;AAC1F,MAAM,UAAU,aAAa,CAAC,IAAoB,EAAE,WAA8B;IAChF,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC;AAChF,CAAC","sourcesContent":["// The scoring half of the sport adapter contract: the pure members the store driver, the replay, the\n// client's optimistic queue, and the aggregate cores call (RULE-QS-SPORT-903). None issues a Firestore\n// operation or an increment, reads a clock, or mints an id.\n//\n// Every member is typed over every sport's shapes, so any registered adapter is called the same way.\n// The registry chose the adapter by the game's `leagueType`, which is what makes the rollup it is\n// handed that sport's (RULE-QS-SPORT-902); an event of another sport is refused by `validate` and thrown on\n// by `apply` and `undo`, never guessed at.\n\nimport type { GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport type { StatCounterKey } from '../types/sports.js';\nimport { STAT_CORRECTION_EVENT_TYPE, STAT_VOID_EVENT_TYPE } from '../types/stats.js';\nimport type {\n FinalizationNotReadyReason,\n StatEventRecorderField,\n StatEventRefusalCode,\n StatEventSnapshotField,\n StatLineKind,\n} from '../types/stats.js';\nimport type { FinalizedGame, GameContribution } from './contribution.js';\nimport type { GameTeamIds } from './sport-adapter.js';\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\n/**\n * What an event says happened — its type and its payload. Its id, its `recordedAt`, and what the\n * writing core stamps on it are not part of it, so a stored event, a queued one, and a drafted one\n * are all bodies.\n */\nexport type StatEventBody = DistributiveOmit<\n GameStatEvent,\n 'eventId' | 'recordedAt' | StatEventRecorderField | StatEventSnapshotField\n>;\nexport type StatVoidBody = Extract<StatEventBody, { type: typeof STAT_VOID_EVENT_TYPE }>;\nexport type StatCorrectionBody = Extract<\n StatEventBody,\n { type: typeof STAT_CORRECTION_EVENT_TYPE }\n>;\n/** One of a sport's own events, as opposed to the store-level void and correction. */\nexport type SportStatEventBody = Exclude<StatEventBody, StatVoidBody | StatCorrectionBody>;\n\nexport function isStatVoid(event: StatEventBody): event is StatVoidBody {\n return event.type === STAT_VOID_EVENT_TYPE;\n}\n\nexport function isStatCorrection(event: StatEventBody): event is StatCorrectionBody {\n return event.type === STAT_CORRECTION_EVENT_TYPE;\n}\n\n/** Whether `event` is one of the sport events `eventTypes` declares — never a void or a correction. */\nexport function isSportStatEventIn(\n eventTypes: readonly string[],\n event: StatEventBody | undefined,\n): event is SportStatEventBody {\n return event !== undefined && eventTypes.includes(event.type);\n}\n\n/** One team's line in a game's rollup, whichever sport it counts. */\nexport type GameTeamLine = GameStats['teamStats'][string];\n/** One roster entry's line in a game's rollup, whichever sport it counts. */\nexport type GamePlayerLine = GameStats['playerStats'][string];\n\n/** A stat line of any kind as `deriveLine` reads and returns it: its counters, derived fields, and ends. */\nexport type StatLineValues = { readonly [field: string]: number | readonly (number | null)[] };\n\n/** The game an event is validated against: its teams, and the roster entries the batch names. */\nexport interface StatEventRoster {\n /** A team of the game is one of these — never inferred from which team lines a rollup holds. */\n readonly teamIds: readonly string[];\n /** The roster entries the driver read for the batch — never the whole roster. */\n readonly rosterPlayerIds: ReadonlySet<string>;\n}\n\nexport type StatEventVerdict =\n { readonly ok: true } | { readonly ok: false; readonly code: StatEventRefusalCode };\n\nexport const STAT_EVENT_ACCEPTED: StatEventVerdict = { ok: true };\n\nexport function refuseStatEvent(code: StatEventRefusalCode): StatEventVerdict {\n return { ok: false, code };\n}\n\nexport type FinalizationReadiness =\n { readonly ready: true } | { readonly ready: false; readonly reason: FinalizationNotReadyReason };\n\nexport const FINALIZATION_READY: FinalizationReadiness = { ready: true };\n\n/** The fields a `STAT_CORRECTION` may change, on a team line and on a player line. */\nexport interface CorrectableFields {\n readonly team: readonly StatCounterKey[];\n readonly player: readonly StatCounterKey[];\n}\n\nexport interface SportScoring {\n /**\n * Whether a new event may be recorded against `state`: its type is the sport's, its team is one\n * of the game's, every person it names is a roster entry of that team, and its values are a real\n * result. Only the store driver calls it, and only for a new event — a replay never re-validates\n * what was already accepted.\n */\n readonly validate: (\n state: GameStats,\n event: StatEventBody,\n roster: StatEventRoster,\n ) => StatEventVerdict;\n /**\n * `state` with one sport event or one `STAT_CORRECTION` applied. A missing line is created from\n * the sport's empty line (RULE-QS-SPORT-915); a player line whose counters all return to zero is dropped;\n * a team line never is. Throws on a void, another sport's event, or a correction to a field not in\n * `correctableFields`.\n */\n readonly apply: (state: GameStats, event: StatEventBody) => GameStats;\n /** The exact inverse of `apply(target)` for one of the sport's own events (RULE-QS-SPORT-913). */\n readonly undo: (state: GameStats, target: StatEventBody) => GameStats;\n /** `line` with every derived field of its kind; the only caller of any derivation formula. */\n readonly deriveLine: (kind: StatLineKind, line: StatLineValues) => StatLineValues;\n readonly correctableFields: CorrectableFields;\n /** The next round to record for a sport played in rounds, `null` once all are or for any other sport. */\n readonly nextRound: (state: GameStats) => number | null;\n /** Whether the game may be finalized (RULE-QS-SPORT-922). */\n readonly finalizationReadiness: (state: GameStats, game: GameTeamIds) => FinalizationReadiness;\n /**\n * The games-played decision finalize stores: the roster entries credited no game played, from\n * each entry's own line and absence, in roster order (RULE-QS-SPORT-924).\n */\n readonly participation: (\n state: GameStats,\n rosterPlayerIds: readonly string[],\n absentRosterPlayerIds: readonly string[],\n ) => string[];\n /** What a completed game adds to every derived destination (RULE-QS-SPORT-927). */\n readonly contribution: (game: FinalizedGame) => GameContribution;\n}\n\n/** Whether every counter of a line is zero — a player line the rollup then leaves out. */\nexport function isAllZeroLine(line: StatLineValues, counterKeys: readonly string[]): boolean {\n return counterKeys.every((key) => line[key] === 0 || line[key] === undefined);\n}\n"]}
@@ -29,7 +29,7 @@ export interface SportTypeMap {
29
29
  /**
30
30
  * The schema of each of the sport's stat lines, by kind. A stored line is read as this sport's line
31
31
  * through its schema — chosen by the adapter `leagueType` selected, never inferred from the line's
32
- * shape (SPORT-002).
32
+ * shape (RULE-QS-SPORT-902).
33
33
  */
34
34
  export type SportLineSchemas<L extends LeagueType> = (typeof SPORT_LINE_SCHEMAS)[L];
35
35
  /** `never` for a kind the sport keeps no line of. */
@@ -46,7 +46,7 @@ export type ColumnCatalogRow<L extends LeagueType, K extends ColumnCatalogKey> =
46
46
  export type SportColumnCatalogs<L extends LeagueType> = {
47
47
  readonly [K in ColumnCatalogKey]: ColumnCatalog<ColumnCatalogRow<L, K>>;
48
48
  };
49
- /** What shared surfaces render for the sport instead of branching on it (SPORT-004, SPORT-209). */
49
+ /** What shared surfaces render for the sport instead of branching on it (RULE-QS-SPORT-904, RULE-QS-SPORT-929). */
50
50
  export interface SportDisplay<L extends LeagueType> {
51
51
  readonly label: string;
52
52
  readonly iconKey: SportIconKey;
@@ -90,7 +90,7 @@ export interface SportAdapter<L extends LeagueType> extends SportScoring {
90
90
  readonly display: SportDisplay<L>;
91
91
  readonly columns: SportColumnCatalogs<L>;
92
92
  /**
93
- * The rollup of a game with no events (SPORT-105): a team line for each of the game's teams, and
93
+ * The rollup of a game with no events (RULE-QS-SPORT-915): a team line for each of the game's teams, and
94
94
  * no player lines. Every call returns new objects.
95
95
  */
96
96
  readonly emptyStats: (game: GameTeamIds) => SportGameStats<L>;
@@ -1 +1 @@
1
- {"version":3,"file":"sport-adapter.d.ts","sourceRoot":"","sources":["../../src/sports/sport-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sport-adapter.d.ts","sourceRoot":"","sources":["../../src/sports/sport-adapter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EACV,wBAAwB,EACxB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,qBAAqB,EACrB,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAI9B,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;QAC5C,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC;QAC5C,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;KACjD,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;QACzC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;QACzC,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAC;KAC/B,CAAC;IACF,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;QAC1C,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;QAC1C,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAC;KAC/B,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,UAAU,IAAI,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpF,qDAAqD;AACrD,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,YAAY,IAAI,CAAC,CAAC,KAAK,CAC3E,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACvB,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,UAAU,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAKjG,eAAO,MAAM,yBAAyB;;;;CAIqB,CAAC;AAE5D,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,gBAAgB,IAAI,SAAS,CACxF,CAAC,EACD,CAAC,OAAO,yBAAyB,CAAC,CAAC,CAAC,CAAC,CACtC,CAAC;AAEF,yGAAyG;AACzG,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,UAAU,IAAI;IACtD,QAAQ,EAAE,CAAC,IAAI,gBAAgB,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CACxE,CAAC;AAEF,mHAAmH;AACnH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,mGAAmG;IACnG,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACpE,4FAA4F;IAC5F,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;IACrF,iHAAiH;IACjH,QAAQ,CAAC,WAAW,EAAE,eAAe,CACnC,YAAY,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAC9B,YAAY,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CACjC,CAAC;IACF,oFAAoF;IACpF,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,qBAAqB,KAAK,MAAM,CAAC;IACzF,oGAAoG;IACpG,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,0FAA0F;IAC1F,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;CACrC;AAED,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,CAAE,SAAQ,YAAY;IACtE,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IACvB,+GAA+G;IAC/G,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,8FAA8F;IAC9F,QAAQ,CAAC,UAAU,EAAE,SAAS,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC;IAC7D,0GAA0G;IAC1G,QAAQ,CAAC,YAAY,EAAE,SAAS,YAAY,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC;IACjE,QAAQ,CAAC,cAAc,EAAE,SAAS,YAAY,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC;IACrE,gGAAgG;IAChG,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,cAAc,CAAC,CAAC,CAAC,CAAC;IAC9D,oGAAoG;IACpG,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B;AAED,mGAAmG;AACnG,MAAM,MAAM,oBAAoB,GAAG;IAAE,QAAQ,EAAE,CAAC,IAAI,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC;CAAE,CAAC;AAEnF,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC"}
@@ -1,7 +1,7 @@
1
1
  // The sport adapter contract: one adapter per sport owns everything about that sport, and every
2
- // shared surface asks it rather than branching on a sport (SPORT-003, SPORT-004). Every member is
2
+ // shared surface asks it rather than branching on a sport (RULE-QS-SPORT-903, RULE-QS-SPORT-904). Every member is
3
3
  // pure — no Firestore operation, no clock, no minted id, no React — so both app roots run the one
4
- // copy (ARCH-101, ARCH-202).
4
+ // copy (RULE-STD-ARCH-101, RULE-STD-ARCH-202).
5
5
  // The line kind each stats table reads its rows from. Standings rows are team season lines; the
6
6
  // game page's rows are player game lines; every player-totals table — league rankings, the team
7
7
  // page, the hall of fame — reads rows shaped as membership rows.
@@ -1 +1 @@
1
- {"version":3,"file":"sport-adapter.js","sourceRoot":"","sources":["../../src/sports/sport-adapter.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,kGAAkG;AAClG,kGAAkG;AAClG,6BAA6B;AAmE7B,gGAAgG;AAChG,gGAAgG;AAChG,iEAAiE;AACjE,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,YAAY,EAAE,eAAe;IAC7B,UAAU,EAAE,YAAY;IACxB,UAAU,EAAE,YAAY;CACiC,CAAC","sourcesContent":["// The sport adapter contract: one adapter per sport owns everything about that sport, and every\n// shared surface asks it rather than branching on a sport (SPORT-003, SPORT-004). Every member is\n// pure — no Firestore operation, no clock, no minted id, no React — so both app roots run the one\n// copy (ARCH-101, ARCH-202).\n\nimport type { z } from 'zod';\nimport type { SPORT_LINE_SCHEMAS } from '../doc-schemas/aggregate-stats.js';\nimport type { SPORT_GAME_STATS_SCHEMAS } from '../doc-schemas/sport-game.js';\nimport type {\n BasketballPlayerStatKey,\n BasketballStatEventType,\n BasketballTeamStatKey,\n CurlingStatEventType,\n CurlingTeamStatKey,\n FootballStatEventType,\n FootballTeamStatKey,\n LeagueType,\n SPORT_KEY_BY_LEAGUE_TYPE,\n SportIconKey,\n} from '../types/sports.js';\nimport type { StatLineKind } from '../types/stats.js';\nimport type { GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { BulkEntry } from './bulk-entry.js';\nimport type { ColumnCatalog, ColumnCatalogKey } from './column-catalog.js';\nimport type { LifetimeSummary } from './lifetime-summary.js';\nimport type { SportScoring } from './scoring.js';\nimport type {\n RoundsDisplay,\n Scoreboard,\n ScoringPanel,\n StatEventLabel,\n StatEventNameResolver,\n StatFieldLabels,\n} from './scoring-display.js';\n\n// Each sport's declared vocabulary, keyed by `leagueType`. Its lines, and the game payload built\n// from them, come from `SPORT_LINE_SCHEMAS`.\nexport interface SportTypeMap {\n readonly Basketball: {\n readonly eventType: BasketballStatEventType;\n readonly teamStatKey: BasketballTeamStatKey;\n readonly playerStatKey: BasketballPlayerStatKey;\n };\n readonly Curling: {\n readonly eventType: CurlingStatEventType;\n readonly teamStatKey: CurlingTeamStatKey;\n readonly playerStatKey: never;\n };\n readonly Football: {\n readonly eventType: FootballStatEventType;\n readonly teamStatKey: FootballTeamStatKey;\n readonly playerStatKey: never;\n };\n}\n\n/**\n * The schema of each of the sport's stat lines, by kind. A stored line is read as this sport's line\n * through its schema — chosen by the adapter `leagueType` selected, never inferred from the line's\n * shape (SPORT-002).\n */\nexport type SportLineSchemas<L extends LeagueType> = (typeof SPORT_LINE_SCHEMAS)[L];\n\n/** `never` for a kind the sport keeps no line of. */\nexport type SportLine<L extends LeagueType, K extends StatLineKind> = z.infer<\n SportLineSchemas<L>[K]\n>;\n\n/** The game `stats` payload of one sport: its team lines and roster-entry lines. */\nexport type SportGameStats<L extends LeagueType> = z.infer<(typeof SPORT_GAME_STATS_SCHEMAS)[L]>;\n\n// The line kind each stats table reads its rows from. Standings rows are team season lines; the\n// game page's rows are player game lines; every player-totals table — league rankings, the team\n// page, the hall of fame — reads rows shaped as membership rows.\nexport const COLUMN_CATALOG_LINE_KINDS = {\n playerTotals: 'membershipRow',\n gamePlayer: 'gamePlayer',\n teamSeason: 'teamSeason',\n} as const satisfies Record<ColumnCatalogKey, StatLineKind>;\n\nexport type ColumnCatalogRow<L extends LeagueType, K extends ColumnCatalogKey> = SportLine<\n L,\n (typeof COLUMN_CATALOG_LINE_KINDS)[K]\n>;\n\n/** A sport's three column catalogs, each over its own row type; a sport missing one does not compile. */\nexport type SportColumnCatalogs<L extends LeagueType> = {\n readonly [K in ColumnCatalogKey]: ColumnCatalog<ColumnCatalogRow<L, K>>;\n};\n\n/** What shared surfaces render for the sport instead of branching on it (SPORT-004, SPORT-209). */\nexport interface SportDisplay<L extends LeagueType> {\n readonly label: string;\n readonly iconKey: SportIconKey;\n /** The profile's lifetime summary for the sport: its totals, and its per-game averages beneath. */\n readonly lifetimeSummary: LifetimeSummary<SportLine<L, 'lifetime'>>;\n /** Each of the sport's event types: its scoring-panel button and its play-by-play label. */\n readonly eventLabels: Readonly<Record<SportTypeMap[L]['eventType'], StatEventLabel>>;\n /** Each counter of a team line and of a player line — what a correction row and the Correct a game form name. */\n readonly fieldLabels: StatFieldLabels<\n SportTypeMap[L]['teamStatKey'],\n SportTypeMap[L]['playerStatKey']\n >;\n /** One play-by-play line for any stored event but a void, a correction included. */\n readonly describeEvent: (event: GameStatEvent, resolve: StatEventNameResolver) => string;\n /** Present only for a sport played in rounds: the header's round line and the scoreboard's grid. */\n readonly rounds?: RoundsDisplay;\n readonly scoreboard: Scoreboard;\n /** The one component of the scoring screen that is the sport's own, named by its kind. */\n readonly scoringPanel: ScoringPanel;\n}\n\n/** The game an empty rollup is built for: its real (non-bye) teams. */\nexport interface GameTeamIds {\n readonly teamIds: readonly string[];\n}\n\n/**\n * One sport's adapter: its facts, its scoring members, its display, its column catalogs, and its\n * bulk entry. The scoring members are typed over every sport, so any registered adapter is called\n * the same way.\n */\nexport interface SportAdapter<L extends LeagueType> extends SportScoring {\n readonly leagueType: L;\n /** The storage key: `sportHallOfFame/{sportKey}`, `historicalStats/{sportKey}`, `lifetimeStats.{sportKey}`. */\n readonly sportKey: (typeof SPORT_KEY_BY_LEAGUE_TYPE)[L];\n /** The sport's own event types; the store-level void and correction belong to every sport. */\n readonly eventTypes: readonly SportTypeMap[L]['eventType'][];\n /** The counters of a team line and of a player line — what the aggregates sum and a ghost link merges. */\n readonly teamStatKeys: readonly SportTypeMap[L]['teamStatKey'][];\n readonly playerStatKeys: readonly SportTypeMap[L]['playerStatKey'][];\n /** Whether the sport keeps `sportHallOfFame/{sportKey}` and the hall-of-fame page serves it. */\n readonly hallOfFame: boolean;\n /** Whether an admin may enter pre-Q-Sports career totals for the sport. */\n readonly historicalBaseline: boolean;\n readonly lineSchemas: SportLineSchemas<L>;\n readonly display: SportDisplay<L>;\n readonly columns: SportColumnCatalogs<L>;\n /**\n * The rollup of a game with no events (SPORT-105): a team line for each of the game's teams, and\n * no player lines. Every call returns new objects.\n */\n readonly emptyStats: (game: GameTeamIds) => SportGameStats<L>;\n /** The sport's bulk-entry form, whose rules `validate` enforces again on every event it becomes. */\n readonly bulkEntry: BulkEntry;\n}\n\n/** Every registered adapter, by `leagueType`. Indexing it with a generic sport keeps the sport. */\nexport type SportAdapterRegistry = { readonly [L in LeagueType]: SportAdapter<L> };\n\nexport type AnySportAdapter = SportAdapterRegistry[LeagueType];\n"]}
1
+ {"version":3,"file":"sport-adapter.js","sourceRoot":"","sources":["../../src/sports/sport-adapter.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,kHAAkH;AAClH,kGAAkG;AAClG,+CAA+C;AAmE/C,gGAAgG;AAChG,gGAAgG;AAChG,iEAAiE;AACjE,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,YAAY,EAAE,eAAe;IAC7B,UAAU,EAAE,YAAY;IACxB,UAAU,EAAE,YAAY;CACiC,CAAC","sourcesContent":["// The sport adapter contract: one adapter per sport owns everything about that sport, and every\n// shared surface asks it rather than branching on a sport (RULE-QS-SPORT-903, RULE-QS-SPORT-904). Every member is\n// pure — no Firestore operation, no clock, no minted id, no React — so both app roots run the one\n// copy (RULE-STD-ARCH-101, RULE-STD-ARCH-202).\n\nimport type { z } from 'zod';\nimport type { SPORT_LINE_SCHEMAS } from '../doc-schemas/aggregate-stats.js';\nimport type { SPORT_GAME_STATS_SCHEMAS } from '../doc-schemas/sport-game.js';\nimport type {\n BasketballPlayerStatKey,\n BasketballStatEventType,\n BasketballTeamStatKey,\n CurlingStatEventType,\n CurlingTeamStatKey,\n FootballStatEventType,\n FootballTeamStatKey,\n LeagueType,\n SPORT_KEY_BY_LEAGUE_TYPE,\n SportIconKey,\n} from '../types/sports.js';\nimport type { StatLineKind } from '../types/stats.js';\nimport type { GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { BulkEntry } from './bulk-entry.js';\nimport type { ColumnCatalog, ColumnCatalogKey } from './column-catalog.js';\nimport type { LifetimeSummary } from './lifetime-summary.js';\nimport type { SportScoring } from './scoring.js';\nimport type {\n RoundsDisplay,\n Scoreboard,\n ScoringPanel,\n StatEventLabel,\n StatEventNameResolver,\n StatFieldLabels,\n} from './scoring-display.js';\n\n// Each sport's declared vocabulary, keyed by `leagueType`. Its lines, and the game payload built\n// from them, come from `SPORT_LINE_SCHEMAS`.\nexport interface SportTypeMap {\n readonly Basketball: {\n readonly eventType: BasketballStatEventType;\n readonly teamStatKey: BasketballTeamStatKey;\n readonly playerStatKey: BasketballPlayerStatKey;\n };\n readonly Curling: {\n readonly eventType: CurlingStatEventType;\n readonly teamStatKey: CurlingTeamStatKey;\n readonly playerStatKey: never;\n };\n readonly Football: {\n readonly eventType: FootballStatEventType;\n readonly teamStatKey: FootballTeamStatKey;\n readonly playerStatKey: never;\n };\n}\n\n/**\n * The schema of each of the sport's stat lines, by kind. A stored line is read as this sport's line\n * through its schema — chosen by the adapter `leagueType` selected, never inferred from the line's\n * shape (RULE-QS-SPORT-902).\n */\nexport type SportLineSchemas<L extends LeagueType> = (typeof SPORT_LINE_SCHEMAS)[L];\n\n/** `never` for a kind the sport keeps no line of. */\nexport type SportLine<L extends LeagueType, K extends StatLineKind> = z.infer<\n SportLineSchemas<L>[K]\n>;\n\n/** The game `stats` payload of one sport: its team lines and roster-entry lines. */\nexport type SportGameStats<L extends LeagueType> = z.infer<(typeof SPORT_GAME_STATS_SCHEMAS)[L]>;\n\n// The line kind each stats table reads its rows from. Standings rows are team season lines; the\n// game page's rows are player game lines; every player-totals table — league rankings, the team\n// page, the hall of fame — reads rows shaped as membership rows.\nexport const COLUMN_CATALOG_LINE_KINDS = {\n playerTotals: 'membershipRow',\n gamePlayer: 'gamePlayer',\n teamSeason: 'teamSeason',\n} as const satisfies Record<ColumnCatalogKey, StatLineKind>;\n\nexport type ColumnCatalogRow<L extends LeagueType, K extends ColumnCatalogKey> = SportLine<\n L,\n (typeof COLUMN_CATALOG_LINE_KINDS)[K]\n>;\n\n/** A sport's three column catalogs, each over its own row type; a sport missing one does not compile. */\nexport type SportColumnCatalogs<L extends LeagueType> = {\n readonly [K in ColumnCatalogKey]: ColumnCatalog<ColumnCatalogRow<L, K>>;\n};\n\n/** What shared surfaces render for the sport instead of branching on it (RULE-QS-SPORT-904, RULE-QS-SPORT-929). */\nexport interface SportDisplay<L extends LeagueType> {\n readonly label: string;\n readonly iconKey: SportIconKey;\n /** The profile's lifetime summary for the sport: its totals, and its per-game averages beneath. */\n readonly lifetimeSummary: LifetimeSummary<SportLine<L, 'lifetime'>>;\n /** Each of the sport's event types: its scoring-panel button and its play-by-play label. */\n readonly eventLabels: Readonly<Record<SportTypeMap[L]['eventType'], StatEventLabel>>;\n /** Each counter of a team line and of a player line — what a correction row and the Correct a game form name. */\n readonly fieldLabels: StatFieldLabels<\n SportTypeMap[L]['teamStatKey'],\n SportTypeMap[L]['playerStatKey']\n >;\n /** One play-by-play line for any stored event but a void, a correction included. */\n readonly describeEvent: (event: GameStatEvent, resolve: StatEventNameResolver) => string;\n /** Present only for a sport played in rounds: the header's round line and the scoreboard's grid. */\n readonly rounds?: RoundsDisplay;\n readonly scoreboard: Scoreboard;\n /** The one component of the scoring screen that is the sport's own, named by its kind. */\n readonly scoringPanel: ScoringPanel;\n}\n\n/** The game an empty rollup is built for: its real (non-bye) teams. */\nexport interface GameTeamIds {\n readonly teamIds: readonly string[];\n}\n\n/**\n * One sport's adapter: its facts, its scoring members, its display, its column catalogs, and its\n * bulk entry. The scoring members are typed over every sport, so any registered adapter is called\n * the same way.\n */\nexport interface SportAdapter<L extends LeagueType> extends SportScoring {\n readonly leagueType: L;\n /** The storage key: `sportHallOfFame/{sportKey}`, `historicalStats/{sportKey}`, `lifetimeStats.{sportKey}`. */\n readonly sportKey: (typeof SPORT_KEY_BY_LEAGUE_TYPE)[L];\n /** The sport's own event types; the store-level void and correction belong to every sport. */\n readonly eventTypes: readonly SportTypeMap[L]['eventType'][];\n /** The counters of a team line and of a player line — what the aggregates sum and a ghost link merges. */\n readonly teamStatKeys: readonly SportTypeMap[L]['teamStatKey'][];\n readonly playerStatKeys: readonly SportTypeMap[L]['playerStatKey'][];\n /** Whether the sport keeps `sportHallOfFame/{sportKey}` and the hall-of-fame page serves it. */\n readonly hallOfFame: boolean;\n /** Whether an admin may enter pre-Q-Sports career totals for the sport. */\n readonly historicalBaseline: boolean;\n readonly lineSchemas: SportLineSchemas<L>;\n readonly display: SportDisplay<L>;\n readonly columns: SportColumnCatalogs<L>;\n /**\n * The rollup of a game with no events (RULE-QS-SPORT-915): a team line for each of the game's teams, and\n * no player lines. Every call returns new objects.\n */\n readonly emptyStats: (game: GameTeamIds) => SportGameStats<L>;\n /** The sport's bulk-entry form, whose rules `validate` enforces again on every event it becomes. */\n readonly bulkEntry: BulkEntry;\n}\n\n/** Every registered adapter, by `leagueType`. Indexing it with a generic sport keeps the sport. */\nexport type SportAdapterRegistry = { readonly [L in LeagueType]: SportAdapter<L> };\n\nexport type AnySportAdapter = SportAdapterRegistry[LeagueType];\n"]}
@@ -16,7 +16,7 @@ export declare function statEventIdsToRead(submissions: readonly StatEventSubmis
16
16
  export interface AcceptedStatEvent {
17
17
  readonly eventId: string;
18
18
  readonly sequence: number;
19
- /** The document's fields but the recorder facts, which the writing core stamps (ARCH-106). */
19
+ /** The document's fields but the recorder facts, which the writing core stamps (RULE-STD-ARCH-106). */
20
20
  readonly fields: Readonly<Record<string, unknown>>;
21
21
  }
22
22
  export interface RecordedStatEventResult {
@@ -1 +1 @@
1
- {"version":3,"file":"store-driver.d.ts","sourceRoot":"","sources":["../../src/sports/store-driver.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,KAAK,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAExF,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvE,iDAAiD;AACjD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAQxF;AAED,uGAAuG;AACvG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,sBAAsB,GAC9B;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,oFAAoF;IACpF,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACrD,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACjD,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/F,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACrD,iGAAiG;IACjG,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;CACtE;AAkED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,sBAAsB,CA4DlF"}
1
+ {"version":3,"file":"store-driver.d.ts","sourceRoot":"","sources":["../../src/sports/store-driver.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,KAAK,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAExF,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvE,iDAAiD;AACjD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAQxF;AAED,uGAAuG;AACvG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uGAAuG;IACvG,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,sBAAsB,GAC9B;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,oFAAoF;IACpF,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACrD,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACjD,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/F,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACrD,iGAAiG;IACjG,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;CACtE;AAkED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,sBAAsB,CA4DlF"}
@@ -1,10 +1,10 @@
1
1
  // The store driver's decision half: what one scoring write does with the batch of events it was
2
2
  // sent, given what the game already holds. It owns ids, the sequence, void resolution, and the fold;
3
- // everything sport-shaped is the adapter's (SPORT-003). The writing core reads the game, the stored
3
+ // everything sport-shaped is the adapter's (RULE-QS-SPORT-903). The writing core reads the game, the stored
4
4
  // events the batch names, and the roster entries it names, hands them here, and writes what comes
5
5
  // back — one transaction, all or nothing.
6
6
  //
7
- // An event id is the idempotency key (SPORT-102): an id already stored, or earlier in the batch, is
7
+ // An event id is the idempotency key (RULE-QS-SPORT-912): an id already stored, or earlier in the batch, is
8
8
  // acknowledged when its content matches and never applied again, so re-sending a batch whose first
9
9
  // send timed out after committing changes nothing. A void's id is derived from its target, so a
10
10
  // second undo of one event is the same document and is acknowledged too.