Documenting the protocols, structures and concepts used by Hyperscape.
AyeZeeBB
2026-02-17 d75b75ba0dd53a5edbb9bbd4ac2aa92f85c941e4
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.
2 files added
637 ■■■■■ changed files
03_ubisoft_services/07_stats.md 396 ●●●●● patch | view | raw | blame | history
03_ubisoft_services/08_news.md 241 ●●●●● patch | view | raw | blame | history
03_ubisoft_services/07_stats.md
New file
@@ -0,0 +1,396 @@
# 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
  }
}
```
03_ubisoft_services/08_news.md
New file
@@ -0,0 +1,241 @@
# 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).