Add Ubisoft services docs for stats and news
Add reference documentation for Ubisoft services used by Hyper Scape. Includes a new stats page describing profile stats endpoint, player entities (PlayerStats, PlayerLoadout, PlayerBattlepassData, PlayerTutorialProgression), Ubisoft Connect (Club) actions and rewards, and account status endpoints with examples. Also adds a news page documenting profile/space news endpoints, response structure, placements, link targets, polling interval, example payloads, and notes for serving news from the private Scapegoat server.
| New file |
| | |
| | | # Ubisoft Services: Stats & Player Data |
| | | |
| | | This page documents the stats-related endpoints used by Hyper Scape, including profile stats, player entities, Ubisoft Connect (Club) actions/rewards, and account status. |
| | | |
| | | ## Profile Stats |
| | | |
| | | The game queries aggregate match stats via the profile stats endpoint. |
| | | |
| | | ### Endpoint |
| | | |
| | | ``` |
| | | GET /v1/profiles/stats?profileIds={profileId}&statNames={statNames}&spaceId={spaceId} |
| | | ``` |
| | | |
| | | **Host:** `public-ubiservices.ubi.com` |
| | | |
| | | ### Headers |
| | | |
| | | | Header | Value | |
| | | |--------|-------| |
| | | | Authorization | `Ubi_v1 t={ticket}` | |
| | | | Content-Type | `application/json` | |
| | | | Ubi-AppId | `69e55abd-5aca-4eee-be94-ce3944c72e73` | |
| | | | Ubi-SessionId | `{sessionId}` | |
| | | | Ubi-localeCode | `en-US` | |
| | | |
| | | ### Query Parameters |
| | | |
| | | | Parameter | Description | |
| | | |-----------|-------------| |
| | | | profileIds | The player's profile UUID | |
| | | | statNames | Comma-separated list of stat names to query | |
| | | | spaceId | `3f28fbeb-1eaa-460f-9265-2ef2a47fd152` | |
| | | |
| | | ### Known Stat Names |
| | | |
| | | The game makes two separate stat requests: |
| | | |
| | | **Initial login (lobby load):** |
| | | - `TotalMatches` |
| | | |
| | | **Career stats screen:** |
| | | - `TotalMatches` |
| | | - `TotalWinsMatchType.MatchType.arcade` |
| | | - `TotalWinsMatchType.MatchType.solo` |
| | | - `TotalWinsMatchType.MatchType.squad` |
| | | - `DamageDone` |
| | | - `FinalBlows` |
| | | - `Assists` |
| | | - `Revives` |
| | | |
| | | ### Example Response (Full Stats) |
| | | |
| | | ```json |
| | | { |
| | | "profiles": [ |
| | | { |
| | | "profileId": "805f5ad9-1b7b-4a1d-8eb8-b040008e403f", |
| | | "stats": { |
| | | "Assists": { |
| | | "value": "7", |
| | | "startDate": "2019-01-01T01:00:00Z", |
| | | "endDate": null, |
| | | "obj": {}, |
| | | "lastModified": "2020-07-22T23:03:18.44Z" |
| | | }, |
| | | "DamageDone": { |
| | | "value": "1988", |
| | | "startDate": "2019-01-01T01:00:00Z", |
| | | "endDate": null, |
| | | "obj": {}, |
| | | "lastModified": "2020-07-22T23:03:18.439Z" |
| | | }, |
| | | "FinalBlows": { |
| | | "value": "8", |
| | | "startDate": "2019-01-01T01:00:00Z", |
| | | "endDate": null, |
| | | "obj": {}, |
| | | "lastModified": "2020-07-22T22:49:22.351Z" |
| | | }, |
| | | "Revives": { |
| | | "value": "1", |
| | | "startDate": "2019-01-01T01:00:00Z", |
| | | "endDate": null, |
| | | "obj": {}, |
| | | "lastModified": "2020-07-22T19:49:16.333Z" |
| | | }, |
| | | "TotalMatches": { |
| | | "value": "14", |
| | | "startDate": "2019-01-01T01:00:00Z", |
| | | "endDate": null, |
| | | "obj": {}, |
| | | "lastModified": "2020-07-22T23:03:18.44Z" |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | > **Note:** Stat values are returned as strings, not numbers. |
| | | |
| | | --- |
| | | |
| | | ## Player Entities |
| | | |
| | | The game stores per-player data as "entities" on the Ubisoft profile system. Each entity has a type, a name, a JSON `obj` payload, and a revision number for optimistic concurrency. |
| | | |
| | | ### List All Entities |
| | | |
| | | ``` |
| | | GET /v2/profiles/{profileId}/entities?spaceId={spaceId}&offset=0&limit=20 |
| | | ``` |
| | | |
| | | ### List Entities by Type |
| | | |
| | | ``` |
| | | GET /v2/profiles/{profileId}/entities?type={type}&spaceId={spaceId}&offset=0&limit=20 |
| | | ``` |
| | | |
| | | ### Update Entity |
| | | |
| | | ``` |
| | | PUT /v2/profiles/entities/{entityId} |
| | | ``` |
| | | |
| | | The PUT body must include the current `revision` number. The server increments it on success. |
| | | |
| | | ### Entity Types |
| | | |
| | | The following entity types were observed for Hyper Scape: |
| | | |
| | | | Type | Name | Description | |
| | | |------|------|-------------| |
| | | | BlockList | BlockList | Player's blocked users list | |
| | | | PlayerBattlepassData | PlayerBattlepassData | Battle pass progress tracking | |
| | | | PlayerTutorialProgression | PlayerTutorialProgression | Tutorial completion flags | |
| | | | PlayerStats | PlayerStats | Playtime tracking (written by the game client) | |
| | | | PlayerLoadout | contender | Equipped items / loadout slots | |
| | | |
| | | ### PlayerStats Entity |
| | | |
| | | This entity is **read and written** by the game client every session. It tracks playtime and is distinct from the profile stats endpoint above. |
| | | |
| | | **Entity ID (example):** `617486ea-e941-4fd0-a322-24bc6aa9e8c9` |
| | | |
| | | #### Example Response |
| | | |
| | | ```json |
| | | { |
| | | "entities": [ |
| | | { |
| | | "entityId": "617486ea-e941-4fd0-a322-24bc6aa9e8c9", |
| | | "profileId": "805f5ad9-1b7b-4a1d-8eb8-b040008e403f", |
| | | "spaceId": "3f28fbeb-1eaa-460f-9265-2ef2a47fd152", |
| | | "type": "PlayerStats", |
| | | "editable": true, |
| | | "name": "PlayerStats", |
| | | "tags": [], |
| | | "obj": { |
| | | "playerstats": [ |
| | | { "name": "pc_totaltime", "value": 13085 }, |
| | | { "name": "solotime", "value": 1709 }, |
| | | { "name": "squadtime", "value": 4964 } |
| | | ] |
| | | }, |
| | | "lastModified": "2026-02-17T06:53:17.897Z", |
| | | "revision": 136 |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | #### PlayerStats Fields |
| | | |
| | | | Field | Description | |
| | | |-------|-------------| |
| | | | pc_totaltime | Total time played on PC (seconds) | |
| | | | solotime | Time played in solo mode (seconds) | |
| | | | squadtime | Time played in squad mode (seconds) | |
| | | |
| | | #### Example PUT Request |
| | | |
| | | The game client sends this on every session to update playtime: |
| | | |
| | | ```json |
| | | { |
| | | "profileId": "805f5ad9-1b7b-4a1d-8eb8-b040008e403f", |
| | | "spaceId": "3f28fbeb-1eaa-460f-9265-2ef2a47fd152", |
| | | "type": "PlayerStats", |
| | | "name": "PlayerStats", |
| | | "tags": [], |
| | | "obj": { |
| | | "playerstats": [ |
| | | { "name": "pc_totaltime", "value": 13085 }, |
| | | { "name": "solotime", "value": 1709 }, |
| | | { "name": "squadtime", "value": 4964 } |
| | | ] |
| | | }, |
| | | "revision": 136 |
| | | } |
| | | ``` |
| | | |
| | | ### PlayerBattlepassData Entity |
| | | |
| | | Tracks which battle pass tier rewards the player has seen. |
| | | |
| | | ```json |
| | | { |
| | | "entityId": "51b7f3e3-c5e6-456c-983d-d47bce7289f8", |
| | | "type": "PlayerBattlepassData", |
| | | "name": "PlayerBattlepassData", |
| | | "obj": { |
| | | "seasonId": "d0127f6a-a802-430f-81b1-9cac9c45c156", |
| | | "lastUnlockedFreeTierSeen": 2, |
| | | "lastFreeTierRewardsSeen": 3, |
| | | "lastPremiumTierRewardsSeen": 0 |
| | | }, |
| | | "revision": 8 |
| | | } |
| | | ``` |
| | | |
| | | ### PlayerTutorialProgression Entity |
| | | |
| | | Tracks which tutorials and cinematics the player has completed. Uses hex hash IDs for most tutorial steps, plus two human-readable fields: |
| | | |
| | | | Field | Value | Description | |
| | | |-------|-------|-------------| |
| | | | IsCinematicCompleted | 1 | Intro cinematic watched | |
| | | | TutorialDone | 1 | Tutorial completed | |
| | | | 0x000000417185FCF9 | 0/1 | Tutorial step flag | |
| | | | 0x000000417185FF49 | 0/1 | Tutorial step flag | |
| | | | ... | ... | Additional tutorial step flags | |
| | | |
| | | ### PlayerLoadout Entity |
| | | |
| | | Stores the player's equipped items across all loadout slots: |
| | | |
| | | ```json |
| | | { |
| | | "entityId": "8d5152c2-138d-4f46-a470-65b81869c2ec", |
| | | "type": "PlayerLoadout", |
| | | "name": "contender", |
| | | "obj": { |
| | | "objects": [ |
| | | { "slotid": 0, "objid": "0x000000402BB7491A" }, |
| | | { "slotid": 1, "objid": "0x000000402BB6F408" }, |
| | | { "slotid": 3, "objid": "0x000000402BB5C386" } |
| | | ], |
| | | "emote": [], |
| | | "weapon": [], |
| | | "generic": [] |
| | | }, |
| | | "revision": 5 |
| | | } |
| | | ``` |
| | | |
| | | Slot IDs map to specific equipment positions (champion, hacks, weapons, skins, etc). The `objid` values are item hex IDs from the item catalog. |
| | | |
| | | --- |
| | | |
| | | ## Ubisoft Connect (Club) Actions |
| | | |
| | | The game fetches Ubisoft Connect challenges (called "actions") for the Hyper Scape space. |
| | | |
| | | ### Endpoint |
| | | |
| | | ``` |
| | | GET /v1/profiles/{profileId}/club/actions?fields=requiredActions,requiredRewards,requiredSpace,requiredForRewards&age=21&spaceId={spaceId}&limit=100&locale=en-US&onlyBadges=true |
| | | ``` |
| | | |
| | | A second variant also fetches hidden actions: |
| | | |
| | | ``` |
| | | GET /v1/profiles/{profileId}/club/actions?fields=requiredForActions,requiredForRewards,requiredSpace&age=21&spaceId={spaceId}&limit=100&locale=en-US&includeHidden=true |
| | | ``` |
| | | |
| | | ### Example Action |
| | | |
| | | ```json |
| | | { |
| | | "spaceId": "3f28fbeb-1eaa-460f-9265-2ef2a47fd152", |
| | | "profileId": "805f5ad9-1b7b-4a1d-8eb8-b040008e403f", |
| | | "id": "1", |
| | | "value": 0, |
| | | "activationDate": "2020-08-10T22:57:25Z", |
| | | "available": true, |
| | | "name": "Ninety-eight to Go\u2026", |
| | | "xp": 0, |
| | | "description": "Eliminate the first of many, many contenders.", |
| | | "isBadge": false, |
| | | "completionDate": "2026-02-17T05:59:48.6116074Z", |
| | | "statName": null, |
| | | "statCompletionThreshold": null, |
| | | "images": [ |
| | | { |
| | | "type": "background", |
| | | "url": "https://static8.cdn.ubi.com/u/Uplay/Games/Common/actions/Action.png" |
| | | } |
| | | ], |
| | | "isCompleted": true, |
| | | "requiredForRewards": [], |
| | | "requiredForActions": [], |
| | | "requiredSpace": null, |
| | | "groups": [] |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Ubisoft Connect (Club) Rewards |
| | | |
| | | The game fetches Ubisoft Connect rewards (downloadable items) for the Hyper Scape space. |
| | | |
| | | ### Endpoint |
| | | |
| | | ``` |
| | | GET /v1/profiles/{profileId}/club/rewards?age=21&spaceId={spaceId}&limit=100&locale=en-US&fields=requiredActions,requiredForActions,requiredSpaces |
| | | ``` |
| | | |
| | | ### Example Reward |
| | | |
| | | ```json |
| | | { |
| | | "id": "APEX_WALLPAPER", |
| | | "profileId": "805f5ad9-1b7b-4a1d-8eb8-b040008e403f", |
| | | "value": 0, |
| | | "creationDate": "2020-08-06T14:31:19.43Z", |
| | | "typeId": 1, |
| | | "typeName": "Downloadable", |
| | | "name": "Exclusive HYPER SCAPE\u2122 Wallpaper Pack", |
| | | "description": "An assortment of HYPER SCAPE\u2122 wallpapers to adorn desktop or mobile device.", |
| | | "platformShared": true, |
| | | "isOwned": true, |
| | | "quantityPurchased": 1, |
| | | "rewardLocation": "https://ubiservices.cdn.ubi.com/.../HYPERSCAPE_WALLPAPER.zip", |
| | | "instruction": "Go to https://ubisoftconnect.com to download this reward.", |
| | | "spaceId": "3f28fbeb-1eaa-460f-9265-2ef2a47fd152", |
| | | "images": [ |
| | | { |
| | | "type": "thumbnail", |
| | | "url": "https://ubiservices.cdn.ubi.com/.../APEX_WALLPAPER_thumbnail.jpg" |
| | | } |
| | | ], |
| | | "xp": 0, |
| | | "requiredSpaces": { "ids": [], "accomplished": false }, |
| | | "requiredActions": { "accomplished": false, "requirements": [] }, |
| | | "requiredForActions": [], |
| | | "groups": [], |
| | | "quantityUsed": 0, |
| | | "rarity": null |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Account Status |
| | | |
| | | The game periodically polls account status. |
| | | |
| | | ### Endpoint |
| | | |
| | | ``` |
| | | GET /v3/users/me?fields=status |
| | | ``` |
| | | |
| | | ### Example Response |
| | | |
| | | ```json |
| | | { |
| | | "status": { |
| | | "autoGeneratedUsername": false, |
| | | "dateOfBirthApproximated": false, |
| | | "invalidEmail": false, |
| | | "missingRequiredInformation": false, |
| | | "pendingDeactivation": false, |
| | | "targetDeactivationDate": null, |
| | | "recoveringPassword": false, |
| | | "passwordUpdateRequired": false, |
| | | "reserved": false, |
| | | "changeEmailPending": false, |
| | | "inactiveAccount": false, |
| | | "generalStatus": "activated", |
| | | "suspiciousActivity": false, |
| | | "locked": false, |
| | | "minor": false, |
| | | "testAccount": false, |
| | | "phoneActivated": true, |
| | | "phoneSanctioned": false, |
| | | "invalidPhone": false, |
| | | "ageVerification": "notVerified", |
| | | "emailChangeInCooldown": null, |
| | | "stolenAccount": false |
| | | } |
| | | } |
| | | ``` |
| New file |
| | |
| | | # Ubisoft Services: News |
| | | |
| | | This page documents the in-game news system used by Hyper Scape. News items appear in the main menu as a carousel banner (Billboard), thumbnail cards (MenuThumbnail), and detail views (MenuDetail). |
| | | |
| | | ## Endpoints |
| | | |
| | | The game fetches news from two endpoints. Both return the same structure but differ in whether `profileId` is populated. |
| | | |
| | | ### Profile News |
| | | |
| | | ``` |
| | | GET /v1/profiles/me/news?spaceId={spaceId} |
| | | ``` |
| | | |
| | | Returns news items with `profileId` set to the authenticated player's profile ID. |
| | | |
| | | ### Space News |
| | | |
| | | ``` |
| | | GET /v1/spaces/news?spaceId={spaceId} |
| | | ``` |
| | | |
| | | Returns news items with `profileId` set to `null`. |
| | | |
| | | ### Headers |
| | | |
| | | | Header | Value | |
| | | |--------|-------| |
| | | | Authorization | `Ubi_v1 t={ticket}` | |
| | | | Content-Type | `application/json` | |
| | | | Ubi-AppId | `69e55abd-5aca-4eee-be94-ce3944c72e73` | |
| | | | Ubi-SessionId | `{sessionId}` | |
| | | | Ubi-localeCode | `en-US` | |
| | | | Ubi-Market | `US` | |
| | | |
| | | **Host:** `public-ubiservices.ubi.com` |
| | | |
| | | ### Query Parameters |
| | | |
| | | | Parameter | Description | |
| | | |-----------|-------------| |
| | | | spaceId | `3f28fbeb-1eaa-460f-9265-2ef2a47fd152` | |
| | | |
| | | ### Polling Interval |
| | | |
| | | From the GameConfigClient, both news endpoints are polled every 900 seconds (15 minutes): |
| | | |
| | | ```json |
| | | "spacesNewsSec": 900, |
| | | "profilesNewsSec": 900 |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Response Structure |
| | | |
| | | Both endpoints return `{ "news": [...] }`. The array contains news item objects. |
| | | |
| | | ### News Item Fields |
| | | |
| | | | Field | Type | Description | |
| | | |-------|------|-------------| |
| | | | spaceId | string | Space UUID (`3f28fbeb-...`) | |
| | | | newsId | string | Unique ID for the news item (e.g. `ignt.25595`) | |
| | | | type | string | News type, always `featured` | |
| | | | placement | string | Where this entry appears: `Billboard`, `MenuThumbnail`, or `MenuDetail` | |
| | | | priority | number | Sort order (lower = higher priority) | |
| | | | displayTime | number | Billboard carousel display time in seconds | |
| | | | publicationDate | string | ISO 8601 publication date (no timezone) | |
| | | | expirationDate | string/null | ISO 8601 expiration date, or `null` for no expiration | |
| | | | locale | string | Locale code (e.g. `en-US`) | |
| | | | title | string | News headline (displayed in uppercase in-game) | |
| | | | contentType | string | Content format, always `plaintext` | |
| | | | body | string | Full news text with `\n` line breaks | |
| | | | isBodyFilledIn | boolean | Always `true` | |
| | | | mediaURL | string | Image URL | |
| | | | mediaType | string | Media type, always `Image` | |
| | | | profileId | string/null | Player's profile UUID (profile news) or `null` (space news) | |
| | | | obj | object | Metadata object | |
| | | | obj.characteristic | string | Badge label: `"New"`, `"Updated"`, or omit for none | |
| | | | summary | string | Short description shown in the thumbnail card | |
| | | | links | array | Action links (see below) | |
| | | | tags | array | String tags for categorization | |
| | | |
| | | --- |
| | | |
| | | ## Placements |
| | | |
| | | Each logical news item needs **3 entries** in the array — one per placement: |
| | | |
| | | | Placement | Where it Shows | Image Size | |
| | | |-----------|---------------|------------| |
| | | | Billboard | Main menu carousel (large banner) | ~946 x 632 px | |
| | | | MenuThumbnail | Small card in the news list | ~494 x 500 px | |
| | | | MenuDetail | Expanded detail view when card is clicked | ~946 x 632 px | |
| | | |
| | | All three entries share the same `newsId`, `title`, `body`, `summary`, and other text fields. Only the `placement` and `mediaURL` differ (Billboard and MenuDetail use the large image, MenuThumbnail uses the small one). |
| | | |
| | | --- |
| | | |
| | | ## Links |
| | | |
| | | Links define what happens when the player clicks the news item's call-to-action button. |
| | | |
| | | | Field | Type | Description | |
| | | |-------|------|-------------| |
| | | | rootLinkNoId | string | Unique UUID for the link | |
| | | | type | string | Link type: `menulink` or `weblink` | |
| | | | param | string | Target — see table below | |
| | | | actionName | string | Button text (e.g. `"visit"`) | |
| | | | actionDescription | string | Optional description, usually empty | |
| | | |
| | | ### Menu Link Targets |
| | | |
| | | | param Value | Navigates To | |
| | | |-------------|-------------| |
| | | | Battlepass | Battle Pass screen | |
| | | | Challenges | Challenges screen | |
| | | | Shop | In-game shop | |
| | | | HallOfChampions | Hall of Champions screen | |
| | | |
| | | ### Web Link |
| | | |
| | | For `type: "weblink"`, the `param` field contains a URL that opens in the player's browser. |
| | | |
| | | --- |
| | | |
| | | ## Example Response |
| | | |
| | | ```json |
| | | { |
| | | "news": [ |
| | | { |
| | | "spaceId": "3f28fbeb-1eaa-460f-9265-2ef2a47fd152", |
| | | "newsId": "ignt.25595", |
| | | "type": "featured", |
| | | "placement": "Billboard", |
| | | "priority": 1, |
| | | "displayTime": 1, |
| | | "publicationDate": "2021-03-30T13:27:00", |
| | | "expirationDate": null, |
| | | "locale": "en-US", |
| | | "title": "SEASON 3 BATTLE PASS AVAILABLE", |
| | | "contentType": "plaintext", |
| | | "body": "The Season 3 Battle Pass is now available! Head to the Battle Pass portal for more details. Complete Challenges to level up your 100-tier Battle Pass!\n\nDon't forget that you can also earn Battle Points by watching HYPER SCAPE\u2122 on Twitch.TV with the Crowncast extension!", |
| | | "isBodyFilledIn": true, |
| | | "mediaURL": "https://ubiservices.cdn.ubi.com/3f28fbeb-1eaa-460f-9265-2ef2a47fd152/news/S3_BP_494x500.png", |
| | | "mediaType": "Image", |
| | | "profileId": "805f5ad9-1b7b-4a1d-8eb8-b040008e403f", |
| | | "obj": { |
| | | "characteristic": "New" |
| | | }, |
| | | "summary": "The Season 3 Battle Pass is now available! Complete the new 100-tier Battle Pass and get awesome exclusive cosmetic in-game rewards!", |
| | | "links": [ |
| | | { |
| | | "rootLinkNoId": "afb511da-1db5-04ac-f326-a50e73952a95", |
| | | "type": "menulink", |
| | | "param": "Battlepass", |
| | | "actionName": "visit", |
| | | "actionDescription": "" |
| | | } |
| | | ], |
| | | "tags": ["S2_BattlePass"] |
| | | }, |
| | | { |
| | | "spaceId": "3f28fbeb-1eaa-460f-9265-2ef2a47fd152", |
| | | "newsId": "ignt.25595", |
| | | "type": "featured", |
| | | "placement": "MenuThumbnail", |
| | | "...": "same fields, same mediaURL for thumbnail" |
| | | }, |
| | | { |
| | | "spaceId": "3f28fbeb-1eaa-460f-9265-2ef2a47fd152", |
| | | "newsId": "ignt.25595", |
| | | "type": "featured", |
| | | "placement": "MenuDetail", |
| | | "mediaURL": "https://ubiservices.cdn.ubi.com/.../S3_BP_946x632.png", |
| | | "...": "same fields, larger image for detail view" |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | ### Original Captured News Items |
| | | |
| | | The last captured response from Ubisoft contained 2 news items (6 entries total — 3 placements each): |
| | | |
| | | | newsId | Title | Links To | Tags | |
| | | |--------|-------|----------|------| |
| | | | ignt.25595 | SEASON 3 BATTLE PASS AVAILABLE | Battlepass (menulink) | S2_BattlePass | |
| | | | ignt.25596 | OBTAIN THE MEMORY SHARDS | Challenges (menulink) | S3_Shards | |
| | | |
| | | --- |
| | | |
| | | ## Serving News for Scapegoat |
| | | |
| | | The existing `NewsController.php` already handles both endpoints. Here's how news items are structured for the private server: |
| | | |
| | | ### Defining a News Item |
| | | |
| | | Each news definition is expanded into 3 placement entries automatically. You only need to define it once: |
| | | |
| | | ```php |
| | | $newsDefinitions = [ |
| | | [ |
| | | 'newsId' => 'custom.001', |
| | | 'type' => 'featured', |
| | | 'priority' => 1, |
| | | 'displayTime' => 1, |
| | | 'publicationDate' => '2026-02-17T00:00:00', |
| | | 'expirationDate' => null, |
| | | 'title' => 'WELCOME TO SCAPEGOAT', |
| | | 'contentType' => 'plaintext', |
| | | 'body' => "Welcome to Scapegoat!\n\nBody text here...", |
| | | 'summary' => 'Short description for the thumbnail card.', |
| | | 'obj' => ['characteristic' => 'New'], |
| | | 'links' => [], |
| | | 'tags' => ['welcome'], |
| | | 'mediaURL_large' => 'https://example.com/banner_946x632.jpg', |
| | | 'mediaURL_small' => 'https://example.com/thumb_494x500.jpg', |
| | | ], |
| | | ]; |
| | | ``` |
| | | |
| | | ### Key Implementation Notes |
| | | |
| | | 1. **Both endpoints return the same data** — the only difference is whether `profileId` is populated (profile news) or `null` (space news). |
| | | |
| | | 2. **Each logical news item = 3 array entries** — one for Billboard, MenuThumbnail, and MenuDetail. All share the same `newsId`. |
| | | |
| | | 3. **Image sizes matter:** |
| | | - Billboard & MenuDetail: ~946 x 632 px (large banner) |
| | | - MenuThumbnail: ~494 x 500 px (small card) |
| | | |
| | | 4. **Characteristics** — Set `obj.characteristic` to `"New"` or `"Updated"` to show a badge, or omit for no badge. |
| | | |
| | | 5. **Menu links** let you navigate the player to in-game screens (Battlepass, Challenges, Shop, HallOfChampions). |
| | | |
| | | 6. **Empty news** — Returning `{"news": []}` is valid and will show no news items in the menu. |
| | | |
| | | 7. **Polling** — The game re-fetches news every 15 minutes (`spacesNewsSec: 900`, `profilesNewsSec: 900` in GameConfigClient). |