From d75b75ba0dd53a5edbb9bbd4ac2aa92f85c941e4 Mon Sep 17 00:00:00 2001
From: AyeZeeBB <nxmbx123@hotmail.com>
Date: Tue, 17 Feb 2026 20:18:31 +0000
Subject: [PATCH] Add Ubisoft services docs for stats and news
---
03_ubisoft_services/07_stats.md | 396 ++++++++++++++++++++++++++++++++++++
03_ubisoft_services/08_news.md | 241 +++++++++++++++++++++
2 files changed, 637 insertions(+), 0 deletions(-)
diff --git a/03_ubisoft_services/07_stats.md b/03_ubisoft_services/07_stats.md
new file mode 100644
index 0000000..379f65a
--- /dev/null
+++ b/03_ubisoft_services/07_stats.md
@@ -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
+ }
+}
+```
diff --git a/03_ubisoft_services/08_news.md b/03_ubisoft_services/08_news.md
new file mode 100644
index 0000000..1502735
--- /dev/null
+++ b/03_ubisoft_services/08_news.md
@@ -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).
--
Gitblit v1.10.0