Competition SDK
From GameSpy SDK
ATLAS Competition SDK
Overview
Most developers would like to add competition to the multiplayer portion of their game, but they don't always have the time or resources to create such a complex system. The GameSpy Competition SDK is an easy-to-use solution that enables developers to insert competition functionality into their games, while maintaining a high level of flexibility and security.
You might be thinking, what is competition? By definition, competition involves striving to achieve dominance or attaining a set goal. In games, this generally refers to the inclusion of developer-defined metrics used to measure a player's performance and compare these stats against others. This could be in the form of a Leader Board which tracks the top players with a given category, such as who has the top all-time win/loss ratio. It can also be in the form of individual accomplishments that tell a player how well they have done in a given area, such as weapon accuracy or percentage of the game completed (for single player games). The Competition SDK allows developers to pick and choose what they deem necessary as statistics by which players can compare themselves to their peers.
The Competition SDK provides an easy web interface in order to customize the statistics and data reported to our backend. Since everything is setup using this simple Administrative site, there is no scripting or custom code required in order to process this data; this is all accomplished via the web interface. The Competition API allows you to send game results to our central servers, which will then go through all received reports and normalize these to create a final report. The normalization process handles any discrepancies that might arise (and applying penalties where necessary) in order to establish an official final report it considers to be the most accurate representation of what took place. Statistics are then stored in our generalized remote storage system called Sake; the game must utilize the GameSpy Sake SDK in order to retrieve player stats.
Features
- Easy-to-use web interface (Admin Site) to create keys/rules for a game.
- Custom Game-specific data or keys submitted in addition to the game results.
- Custom rules that are used to process raw statistical data
- Final stats are stored in Sake storage system
Admin Site (ATLAS web interface)
The Administrative site (http://tools.gamespy.net/atlas/) will allow for creation of keys, processing rules, and stats. If you do not have access to the site, you can gain access by e-mailing devsupport@gamespy.com. The site contains numerous examples designed to help guide you along in creating the necessary elements for adding stats in your game. If you have problems with the site, or any questions that are not directly addressed, please e-mail us at devsupport@gamespy.com
On the Home page of the Admin Site you will see links to create Ruleset(s) to integrate into your C/C++ codebase. These files contain defines for the KEYS, STATS, and ATLAS_RULE_SET_VERSION which can be used when building reports or retrieving stats. To see an example of this the ScRaceSample includes the auto-generated header atlas_Competition_Race_Sample_App_v1.h, which is referenced in the sample code as well.
Custom Keys
Developers can create user-defined keys to submit custom data along with the generic game results (such as win, loss, etc.). Each key that the game reports is paired with a value that represents some relevant information from the game that is either global, player, or team specific.
Custom Processing Rules and Generating Stats
Developers will utilize the ATLAS Admin site in order to create these keys/rules/stats for their game. Custom processing rules are used to process stats via a set of input/output specifications and a selected operation (addition, subtraction, etc.). The inputs can be either previous stats, or keys that were sent along with game results. The output is generally stored as the stats. There will also be common rules that developers can use for their stats calculation.
An example: You might want to keep track of the number of times a particular map is played. To do this you would have a single rule, key, and stat for each map. Then the input key for the game would be something like "boatMap/1" indicating that I just played the "boatMap" map. The rule would be an "incremental" rule operation taking the "boatMap" key and "numBoatMapPlayed" stat as inputs and returning "numBoatMapPlayed" as an output stat. This rule would then increment the "numBoatMapPlayed" stat as long as the "boatMap" key exists with a value of 1.
Security
The biggest threat to competition in online gaming lies in cheating; individuals who want to hack the system in order to gain an unfair advantage. As it currently stands, many players are reluctant to play in online competitions because of this. In order to prevent foul play and help retain the ideal of fair-play in online gaming, the Competition SDK provides a built-in solution to ensure the accuracy and the validity of its results.
Authentication/Validation of Players
The first piece of the puzzle is the use of the GameSpy Authentication service, which validates players. It allows the Competition backend to verify a player is really who he claims to be. Each player involved in a game must obtain a certificate that will be sent along with the results to the Competition service. The certificate will act as a signature of that player, ensuring that the results being submitted are not from an unknown source.
Authoritative v. Collaborative Reports
Next, the SDK allows games to send authoritative reports or collaborative reports. An authoritative report is the equivalent of an official referee-type report of what occurred during gameplay, therefore there must be at least one authoritative report per session that contains the results for all players and teams. A collaborative report contains unofficial data in order to collaborate with the other data being submitted. Unlike an authoritative report, these reports are not as restricted and may contain more information that may be specific to each player. For more information about how to best organize these reports sent, see Appendices II-III below for use-case scenarios regarding typical game types and the reports submitted.
Report Encryption
Currently, the SDK has SSL encryption for encrypting its report submissions. While we feel this is sufficient as a starting basis (in addition to the other securities used in the SDK), report encryption is still a work in progress and other options are being explored to provide further enhanced security.
Tips for Host Migration
For Host migration, the only difference will be that the newly chosen host will change their intention mid-game (if it is not already authoritative, see use-case scenarios below) to submit an authoritative report, as they are now considered the new referee (or the official view) of the game. Also, the Competition system works from "more is better" perspective when it comes to reports, so all players in the game should make sure to send a report in order to provide the most accurate statistics results. Note that for peer-peer games that allow Host migration & Late Entry, this is very specific scenario covered below in Appendix V.
Normalization of Reports
The Competition normalizes reports in order to analyze the game results and provide the best possible explanation for what took place. The normalizer will:
- handle irregularities, such as disconnects or host migration
- handle discrepancies between reported results
Getting Started
Here is a quick rundown of how the process works. Data is submitted as a report to the backend in terms of key/value pairs where the Keys have been predefined on the Admin Site and referenced via the Key ID. The backend processes this data based on the Rules you have defined and places this output into a Stat. These Stats are then generated as records within the Sake database, corresponding to the appropriate table for the Rule Type (ie. GameStats_vX, PlayerStats_vX, TeamStats_vX, or StaticStats_vX) - see Appendix I for more information about retrieving stats.
The Rule Type is very specific to the type of Stats being stored:
GameStats - Game-specific data, not related to any specific player or to any specific team. Because of this,
you will equivalently have 1 record in this table, corresponding to the overall game. The type of Stats
stored here could be something like the number of times a given map has been played, or the aggregate total
of bullets fired in the game (from all players).
PlayerStats – Player-specific data. 1 record per player. This contains any stats related directly to that
player, such as number of overall kills, average kills, average wins, losses, total wins, losses, number of
bullets fired, etc.
TeamStats - Team-specific data. 1 record per team.
StaticStats - global static data used in processing other Stats. 1 record in this table for all Static stats.
Let's say you want to create a PLAYER_HIGH_SCORE. Here's how:
- First you would need to create the Key which would indicate the data submitted in a report to the system. In this case, the key would be the player’s score. Let’s call this key KEY_SCORE.
- In your implementation of the SDK, the report would submit data for each player’s score during the match. The key ID here must match the ID for the KEY_SCORE key in order to indicate the value submitted corresponds to this key. You can generate a Ruleset Header File from the main page of the Admin site (at the bottom) which contains defines for these Key IDs you can reference in your game. For example, if KEY_SCORE had ID #1, then the define would be #define KEY_SCORE 1 so that your code would reference the KEY_SCORE when adding data to the report.
- Next, a Stat needs to be created which is used to hold the Statistic generated from the processing rule. Let’s call this STAT_HIGH_SCORE.
- This STAT_HIGH_SCORE is generated via a rule that we need to create. So we can make a RULE_HIGH_SCORE, which is a Player-type rule that takes KEY_SCORE and STAT_HIGH_SCORE as input, applies the Maximum operation to the inputs, and outputs to STAT_HIGH_SCORE. The Player type rule indicates that this is a per-player based Statistic.
- Once you a report has been submitted and processed, a record will be generated in the PlayerStats_vX (where X indicates the ruleset version number) table in Sake containing your Stat. See Appendix I below to see how you can retrieve this data via the Sake SDK.
Dependencies
-
The Competition SDK is dependent upon the following GameSpy SDKs - in order to use
the Competition SDK you must also include these packages. The latest versions of
these files are available from http://www.gamespy.net/secure/download/.
- Common Code
- The Competition SDK uses the GameSpy Common Code package. Once you have both this package and the Competition package, both need to be to be extracted into a single directory where all GameSpy SDKs can be stored and easily referenced. An example directory structure might look like the following:
- GameSpy HTTP SDK
- The Competition SDK uses this SDK to send requests and receive responses. It is important to have this SDK along with the common code.
- GameSpy Authentication Service
- Authentication services are used to obtain login certificates for the Competition SDK. These files are included with the Competition SDK download and located in the webservices folder.
- GameSpy Sake SDK
- Games must use the Sake SDK to retrieve the stats the Competition system stores in the Sake backend.
\Gamespy \common \sc \webservices
SDK Implementation
Before using Competition, a game must have first performed the standard GameSpy Availability Check. This ensures that the GameSpy backend is available, and that the current game has access to the backend. If the game has not performed the availability check prior to initializing the SDK, the call to scInitialize will return SCResult_NO_AVAILABILITY_CHECK.
This section explains the necessary steps in order to implement the basics of the
Competition SDK. The following is a brief summary of the steps for implementation:
- The SDK and all dependent components are initialized, so they are ready for use
- Player's login. Game authenticates each player via the auth service and retrieves a login certificate used by the SDK to prove a player's authenticity.
- Host creates a game session and distributes
- All players set their report intentions, which describes the type of report (authoritative or collaborative) being submitted.
- Gameplay begins, stats are recorded.
- When the game session is complete, all players create a report with the stats recorded. This report contains (i) global, (ii) player, and (iii) team data, submitted in that order.
- The reports are submitted to the Competition Backend, to be processed according to the rules setup using the Admin site.
1. Initialization
Before doing anything with the Competition SDK itself, you must start the GameSpy core using gsCoreInitialize. The core allows the Competition SDK to initiate and complete its tasks. The authentication service included with the SDK will also require the core to be initialized before use.
void gsCoreInitialize();
Once the core has started, initialize the Competition SDK using scInitialize. The function will return a SCResult for error checking. The actual object that the game needs to keep track of is the SCInterfacePtr. Most functions (except those with Report in the name) will require the SCInterfacePtr for their corresponding operations or retrieval of data.
SCResult scInitialize ( int theGameId, //GAMEID assigned to you by GameSpy SCInterfacePtr * theInterfaceOut //pointer to the Competition object used in subsequent SDK calls );
2. Login and Authentication
Before creating a session or submitting reports, players will need to login and authenticate themselves via the auth service included in the SDKs. To login under a GameSpy Presence (GameSpy ID) account the game should call wsLoginProfile. If the game has not performed the standard GameSpy Availability Check prior to this login attempt, it will fail with a result of WSLogin_NoAvailabilityCheck.
gsi_u32 wsLoginProfile ( int partnerCode, int namespaceId, const char * profileNick, //profile nickname associated with the player's GameSpy Presence account const char * email, //email address associated with the player's GameSpy Presence account const char * password, //password associated with the player's GameSpy Presence account const char * cdkeyhash, //cdkey hash associated with the player's GameSpy Presence account (optional) WSLoginCallback callback, //the callback returned when the login process is complete. void * userData //optional user-defined data passed to the callback. );
- partnerCode
- The partnerid assigned to you by GameSpy (note: not all games use a separate partnerspace). For most games, this will use the generic GameSpy partnerspace, WSLogin_PARTNERCODE_GAMESPY.
- namespaceId
- The namespaceid assigned to you by GameSpy (note: not all games use a separate namespace). If your game does not use unique nicks, you can use WSLogin_NAMESPACE_SHARED_NONUNIQUE. For games using the GameSpy shared default unique nick namespace, use WSLogin_NAMESPACE_SHARED_UNIQUE.
Within the login callback, the WSLoginResponse object will contain the login certificate and private data which will be passed to subsequent Competition SDK calls. These values should be stored for later use:
- GSLoginCertificate mCertificate
- GSLoginPrivateData mPrivateData
3. Creating a session
Once the Host has completed his login, he can create a session using the login certificate and private data mentioned above. A session is generally created for each unique game instance (i.e. a match with a clear winner/loser or end criteria), but is not limited to this:
SCResult scCreateSession ( SCInterfacePtr theInterface, //pointer to the initialized Competition object const GSLoginCertificate * theCertificate, const GSLoginPrivateData * thePrivateData, SCCreateSessionCallback theCallback, //Callback which returns once the request is complete gsi_time theTimeoutMs, //Optional timeout parameter to cancel the request if the timeout is met void * theUserData );
This function will send request to the Competition backend to create a session. If the result of this call is anything other than SCResult_NO_ERROR, this indicates an error has occured. Once a session has been created, the host can retrieve the session ID by calling scGetSessionId:
const char * scGetSessionId(const SCInterfacePtr theInterface);
The host will then need to distribute this session ID to the clients. Each client will then set his session ID with the SDK using scSetSessionId before setting his report intention. The session ID has a constant length of SC_SESSION_GUID_SIZE.
SCResult SC_CALL scSetSessionId ( const SCInterfacePtr theInterface, const gsi_u8 theSessionId[SC_SESSION_GUID_SIZE] );
Note that once a session is created, the backend begins a countdown. If a report for a designated session has not been received within 10 hours from creation, the session times out and is no longer valid. Once the first report has been received, this timeout period decreases to 2 minutes and reports will timeout if not received before this limit expires. Note that a session will never need to be explicitly cancelled as it will eventually timeout if no reports for it have been received.
4. Setting the Report Intention
At this point, the host and clients need to set their report intentions. These intentions should be set by all players submitting a report, prior to starting gameplay.
As a general rule of thumb, the current host will always submit an authoritative report and the clients will submit collaborative reports - this is done by flagging the isAuthoritative parameter when setting intention. However, depending on the game type (i.e. RTS versus FPS) being played, players may set different intentions for themselves. In addition, if Host Migration is involved, these intentions may change mid-game. For more specifics on this, please see Appendices II-III. Also, players can set multiple intentions in order to send multiple reports - a scenario for why this might be used is described in the use-case examples below:
SCResult scSetReportIntention
(
const SCInterfacePtr theInterface,
const gsi_u8 theConnectionID[SC_CONNECTION_GUID_SIZE], //NULL if unused
gsi_bool isAuthoritative, //gsi_true for authoritative snapshots, gsi_false for collaborative
const GSLoginCertificate * theCertificate, //login certificate obtained during authentication
const GSLoginPrivateData * thePrivateData, //private data obtained during authentication
SCSetReportIntentionCallback theCallback,
gsi_time theTimeoutMs,
void * theUserData
);
After setting his intention, each player should retrieve his connection ID (unless returning to a match and using their previous one) which will be used upon submitting a report. This is done by calling scGetConnectionId. The one caveat here is that the host, or any player submitting an authoritative snapshot, will be reporting data for all players and thus needs to store the connection IDs for all players in addition to their own. Players should therefore exchange connection IDs with one another before beginning play.
const char * scGetConnectionId(const SCInterfacePtr theInterface );
Once all intentions have been set, gameplay can begin and the game should begin recording stats for that game session.
5. Creating the Report
Once the game session is complete, everyone who participated in the game should submit a report. To do this, each player will first need to create a report object by calling scCreateReport. This report should be created at the end of a game session to ensure the most accurate values for the player/team count. Note that theHeaderVersion parameter corresponds to the ATLAS_RULE_SET_VERSION located in the auto-generated from the Admin site. This header file can be retrieved after keys for the game have been created on the Admin site (click "Download Ruleset Header File" on the home page for your game).
SCResult scCreateReport ( const SCInterfacePtr theInterface, gsi_u32 theHeaderVersion, //version number obtained via Admin site where keys are created gsi_u32 thePlayerCount, //the number of players who participated in the match gsi_u32 theTeamCount, //the number of teams in the match const SCReportPtr * theReportOut //pointer to the report object used for subsequent calls );
The game should keep track of theReportOut report object for adding stats in key/value pairs to the report. This is done in three stages, for each type of data: global, player, and team data. This should be done in the order shown below so that errors do not occur; (a) Global, (b) Player, and finally (c) Team data:
a. Global Data
Before submitting each type of data, the game must first inform the competition SDK of the data it is about to report. This is done by calling the appropriate scReportBegin* function before submitting this type of data to the report. For global data this function is:
SCResult scReportBeginGlobalData(SCReportPtr theReportData);
After this call is made, the game will submit its data by calling either scReportAddIntValue or scReportAddStringValue.
SCResult scReportAddIntValue ( SCReportPtr theReportData, //pointer to the report object gsi_u16 theKeyId, //the key value being reported gsi_i32 theValue //the value for the reported key ); SCResult scReportAddStringValue ( SCReportPtr theReportData, gsi_u16 theKeyId, const gsi_char * theValue );
b. Player Data
Next the game will submit player data. First the game should notify the Competition SDK it plans to report player data by calling scReportBeginPlayerData:
SCResult scReportBeginPlayerData(SCReportPtr theReportData);
The game will indicate each new player to be reported by calling scReportBeginNewPlayer:
SCResult scReportBeginNewPlayer(SCReportPtr theReportData);
The game will then call scReportSetPlayerData to set the initial
data for this new player to be reported. The connection ID (retrieved
via scGetConnectionId) is passed to the
thePlayerConnectionId parameter in order to designate the player whose
stats are being reported. The theResult parameter is an
enumerated value that describes the final game result for the given player.
Note: theAuthData is currently unused in this version of the SDK.
SCResult scReportSetPlayerData ( SCReportPtr theReport, gsi_u32 thePlayerIndex, const gsi_u8 thePlayerConnectionId[SC_CONNECTIONID_LENGTH], gsi_u32 thePlayerTeamIndex, SCGameResult theResult, gsi_u32 theProfileId, const GSLoginCertificate * theCertificate, const gsi_u8 theAuthData[16] );
After this call is made, the game should report player-specific key/value data by calling either scReportAddIntValue or scReportAddStringValue as it applies. Once the game has finished reporting data for a specific player, if it has more player data to report, it should begin this process again starting with scReportBeginNewPlayer and repeating the above steps, until data for all players has been submitted.
c. Team Data
Lastly, the game should notify the SDK that is about to report team data:
SCResult scReportBeginTeamData(SCReportPtr theReportData);
Just as with player data, the game will then call the team equivalent scReportBeginNewTeam followed by scReportSetTeamData to tell the SDK which team data is about to be reported. The theResult parameter is an enumerated value that describes the final game result for the given team.
SCResult scReportBeginNewTeam(SCReportPtr theReportData);
SCResult scReportSetTeamData ( SCReportPtr theReport, gsi_u32 theTeamIndex, SCGameResult theResult );
Once the report is complete and ready for submittal, the game should call scReportEnd to indicate its completion.
SCResult scReportEnd ( SCReportPtr theReport, gsi_bool isAuth, //gsi_true for authoritative snapshots, gsi_false for collaborative SCGameStatus theStatus //enum describing the completion status of the game );
For SCGameStatus reporting, the game should do the following. As long as the game finished properly, and no one disconnected during the course of play, then all players in the match should submit SCGameStatus_COMPLETE reports. If any members disconnected during play, but the game was finished completely, then all players in the match should submit SCGameStatus_PARTIAL reports indicating that disconnects occured. For any players who do not complete the match, a SCGameStatus_BROKEN report should be submitted. Thus if the game did not completely finish, all players will submit broken reports. The only case that will trigger an invalid report is if reports for the same game describe status as both SCGameStatus_COMPLETE and SCGameStatus_PARTIAL. Since COMPLETE indicates that all players finished the game w/o a disconnect and PARTIAL indicates that disconnects occured, at no time should a game report both complete and partial - this will be seen as an exploit and invalidate the report.
For a better example of the report submission process, see the Competition SDK sample application and Appendices II-III for game type specific usage scenarios.
6. Submitting the Report
Once a report has been completed with a call to scReportEnd, the game should then submit the report to the Competition backend by calling:
SCResult scSubmitReport ( const SCInterfacePtr theInterface, const SCReportPtr theReport, gsi_bool isAuthoritative, const GSLoginCertificate * theCertificate, const GSLoginPrivateData * thePrivateData, SCSubmitReportCallback theCallback, gsi_time theTimeoutMs, void * theUserData );
For sending an authoritative support the game should pass gsi_true to the isAuthoritative parameter. This should match the intention that the player set (refer to scSetReportIntention). If the result of this call is anything other than SCResult_NO_ERROR, this indicates an error has occured.
Once the report has been submitted, the backend will send back the result of the submission via the SCSubmitReportCallback. This callback will indicate to the game if any errors during the submission. An invalid certificate or invalid private data will cause the operation to fail. An incomplete or empty report will also cause this operation to fail.
7. Thinking
All interface functions that have callbacks will require the game to call scThink. In addition the value returned by this function should also be checked in case any problems occur:
SCResult scThink(SCInterfacePtr theInterface);
Remember to call this function in the main loop. All SDK calls should be made from within the same thread. See the following Knowledge Base entry for more information: Are your SDKs thread-safe?.
The authentication service requires a call to the Gamespy Core think function since it uses a different service for authentication:
void gsCoreThink(gsi_time theMs);
8. Shutting Down
Shutting down the Competition SDK is done using the following, it will take care of cleaning up resources used by the SDK:
SCResult scShutdown(SCInterfacePtr theInterface);
In addition, the game should clean up the Gamespy core by using the function:
void gsCoreShutdown();
Appendix I: Retrieving Stats
The Competition SDK only reports gamedata to the backend, it does not retrieve it. When stats are created via the reported results they are stored into the Sake database. The GameSpy Sake SDK is used to retrieve Stats. Please refer to the Sake documentation for more in-depth information about implementing the Sake SDK.
To begin, developers can access the Sake Admin website at http://tools.gamespy.net/SakeAdmin/. After selecting your game, you will be brought to a page to see your game's tables where the stats have been stored. Tables in Sake are automatically generated via the ATLAS Administration site when you create keys for your game. These tables are GameStats_vX, PlayerStats_vX, TeamStats_vX, and StaticStats_vX which you can see under the Sake Admin page for your game. The vX refers to the version number used in both the Admin site as well as when integrating ATLAS (for example, version 1 for a game will have tables defined as GameStats_v1, etc.).
Clicking on "Fields" for any of these tables will show a list of fields which indicate a given Stat. Note that the Stats that are generated from the Rules setup on the ATLAS Admin site will automatically be recorded into these generated Tables, based on the Rule type set when creating the rule. In other words, Game STATS which are generated are stored in the GameStats table, as Player STATS are stored in the PlayerStats table, and so on. You can then retrieve this data via Sake by querying these tables with the field names that correspond to the STATS created on the ATLAS Admin site. The first time you submit a session and the rules process this data into stats, you should see these fields created.
You will use these TableIds and FieldNames in Sake calls to retrieve stats in your game. The easiest method to do so is to use the Sake call sakeSearchForRecords. You can search in a given table, across various fields using an SQL-like filter string along with sorting criteria.
For example, let's say you want to order and show the "top 100-200 entries in descending order of player high scores that are > 50000". You have created a stat called "PLAYER_HIGH_SCORE" which contains a player's current high score. In addition, you have defined a player-type rule in order to calculate this PLAYER_HIGH_SCORE stat; this is all done with ATLAS ruleset version 1. Therefore, after this stat is calculated it will generate a record in the "PlayerStats_v1" table for your game. You would then search for records where the SAKESearchForRecordsInput has the following values:
mTableID = "PlayerStats_v1"
mFieldNames = "PLAYER_HIGH_SCORE", "ownerid"
mNumFields = 2
mFilter = "PLAYER_HIGH_SCORE > 50000"
mSort = "PLAYER_HIGH_SCORE desc"
mOffset = "100"
mMaxRecords = "100"
This would retrieve the result you seek. Each record returned would be that of a player's high score, the
player of which is identified based upon the ownerid (owner's profileid) of the given record.
*Please also refer to Appendix IV in the SAKE Overview for more details about specific Leaderboard queries and optimizations (e.g. getting a player's rank, etc.).*
Appendix II: Use Case - Real-Time Strategy (RTS) Game
The following describes the recommended approach for report submissions with RTS-style games. This is specifically referring to peer-peer games that do not have late entry and may or may not allow Host Migration. The primary difference between this game type and those of dedicated server games is that no single player is really the authoritative view of the game. Since the game is by definition peer-peer, all players essentially act as an official voice of what transpired.
This being the case, we recommend that ALL players submit authoritative reports. This takes care of two common problems. First off, it eliminates the possibility of a 1v1 match where no authoritative report is sent if the host disconnects. By having all players submit authoritative reports, we can ensure that if a host disconnects in a heads -up match, the opponent will report this disconnect as well as this player's data. Secondly, it takes care of any Host Migration issues automatically. By having each player submit an authoritative report, there is no need for players to change their intentions mid-game.
Appendix III: Use Case - First Person Shooter (FPS) Game
Unlike an RTS-style game, the FPS game type we are describing here is that of a dedicated server game allowing late entry. The difference here is that the server itself acts as a dedicated host or official view of gameplay. Since these games allow late entry, it's common to have players joining/leaving/disconnecting during the course of gameplay. Thus, we need to have a single official view of the overall game that can monitor all of the activity. This does not necessarily mean that the host will submit ALL data for every player in the game, as this could grow immensely large over a long game. It simply means the host will need to be the final say for players that disconnect, or the host will corroborate a player's collaborative report with his own.
Therefore the host will submit an authoritative report and all clients will ONLY submit collaborative reports. This means there will only be a single authoritative report per-session (map change, round, etc.), submitted from the host. This authoritative report should only initially contain the host's player data (for non-dedicated hosts) and the game results. In addition to this, the host should ALSO submit collaborative reports for all late-entry players. Doing so will corroborate a player's reported statistics. The only caveat here is that if this player unexpectedly disconnects before the game is complete, we want to ensure their data is reported. To account for this, should a player disconnect unexpectedly, the host will submit that player's data as part of his authoritative report INSTEAD of sending a collaborative report for that player. Doing so will ensure this player's data is recorded even when this player is disconnected from the game. Player's that disconnect normally (in other words, they forced a disconnect by quitting out of the game) should report their collaborative snapshots themselves. This is a recommended approach for any intentional disconnect as illustrated in the Competition Sample Application.
Appendix IV: Troubleshooting / FAQ
The GameSpy Knowledge Base is kept up-to-date with important troubleshooting tips and information about the Competition SDK: http://www.poweredbygamespy.com/secure/kb/categories.php?categoryid=10.