mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 07:01:27 -07:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e62cb19b97 | |||
| e0f802cee5 | |||
| 3d7ea3cf27 | |||
| 4bc5f51b9c | |||
| fef6754aad | |||
| 30b441aa65 | |||
| d441181b03 | |||
| 20196361c8 | |||
| bc12bb07d4 | |||
| 010c83d3ff | |||
| 36f8b01f83 | |||
| 6adb3ab741 | |||
| 9c63f077b8 | |||
| 2af5d5089d | |||
| cae0ebd4ae | |||
| ab623c376b | |||
| 1e26082f2e | |||
| 7d20f96414 | |||
| 99417e052e | |||
| 8260c5abcd | |||
| 740e9efa09 | |||
| 5928e07509 | |||
| 45a07c6ba5 | |||
| 3be2066526 | |||
| 8e452f23eb | |||
| 02c965c2ee | |||
| e07c52ced7 | |||
| 5aea5a4cf9 | |||
| a620cadde0 |
+41
-5
@@ -108,12 +108,21 @@ RECFLARE_DOMAIN=rec.example.com
|
||||
# RECFLARE_PHOTON_VOICE_APP_ID=
|
||||
# RECFLARE_PHOTON_CHAT_APP_ID=
|
||||
|
||||
# The Tachyon voice server (`match`, GET /player/connection-info): the `host:port` the
|
||||
# client is handed as `voiceConnectionInfo`, and its id as `voiceServerId`. EMPTY unless
|
||||
# you set them — no separate voice server. Set both or neither; like the Photon ids they
|
||||
# are not secrets (the client receives them in the clear).
|
||||
# The pool of Tachyon servers sessions are spread across (`match`, GET
|
||||
# /player/connection-info): a COMMA-SEPARATED list of `host:port` entries, one of which
|
||||
# the client is handed as `voiceConnectionInfo`. EMPTY unless you set it — no separate
|
||||
# voice server. Not a secret (the client receives the address in the clear), like the
|
||||
# Photon ids above. One entry is the ordinary single-server case:
|
||||
# RECFLARE_TACHYON_HOST_PORT=127.0.0.1:7777
|
||||
# RECFLARE_TACHYON_NAME=server-1
|
||||
#
|
||||
# List several and each room instance is assigned one for its lifetime, so everybody in
|
||||
# a session lands on the same server while different sessions spread across the pool.
|
||||
# The id the client displays (`voiceServerId`) is generated from an entry's POSITION —
|
||||
# `tachyon-1`, `tachyon-2`, … — so listing one address twice models two server slots on
|
||||
# one box, and inserting an entry renames every server after it. The five below are a
|
||||
# mock pool (RFC 5737 documentation addresses, which answer nothing): tachyon-1/-2 share
|
||||
# a host, as do tachyon-4/-5.
|
||||
# RECFLARE_TACHYON_HOST_PORT=198.51.100.10:7777,198.51.100.10:7778,198.51.100.11:7777,203.0.113.20:7777,203.0.113.20:7778
|
||||
|
||||
# The Photon region every session is pinned to (`match`). Unlike the app ids above this
|
||||
# does default, to `us` (us-east1) — an instance stamped with an empty region is one the
|
||||
@@ -141,3 +150,30 @@ RECFLARE_DOMAIN=rec.example.com
|
||||
# Setting them both is what opens web signup; with either missing it stays closed. See
|
||||
# DEPLOYING.md. Accounts are still created by the game either way, and both `auth` account
|
||||
# caps above apply regardless.
|
||||
|
||||
# The website's benefits claim (www `/claim`): a player proves a role in your Discord and
|
||||
# gets Rec Room Plus. FOUR settings, and ALL FOUR are required — with any missing, the claim
|
||||
# stays closed, `/api/config` reports `benefitsEnabled: false`, and the page and its nav link
|
||||
# never appear. That is the usual reason "I set the secrets and nothing shows up".
|
||||
#
|
||||
# The two CREDENTIALS live in the Secrets Store, like the Turnstile pair above:
|
||||
#
|
||||
# wrangler secrets-store secret create <store-id> --name DISCORD_CLIENT_ID \
|
||||
# --scopes workers --remote
|
||||
# wrangler secrets-store secret create <store-id> --name DISCORD_CLIENT_SECRET \
|
||||
# --scopes workers --remote
|
||||
#
|
||||
# The two IDS are plain vars, and go HERE — they are not secrets, and setting the secrets
|
||||
# alone is not enough. Both are Discord snowflakes: all digits, no letters, copied with
|
||||
# Developer Mode on (right-click the server or role -> Copy ID). Ids, not names.
|
||||
#
|
||||
# ROLE_IDS is a list and ANY one of them qualifies, so several tiers can share the benefit.
|
||||
# SEPARATE THEM WITH COMMAS AND NO SPACES: these knobs are passed to wrangler as `--var`
|
||||
# flags that are word-split, so a value containing a space silently breaks the deploy. (The
|
||||
# worker itself also accepts whitespace, which is fine in wrangler.jsonc but not here.)
|
||||
# RECFLARE_DISCORD_GUILD_ID=1077000000000000000
|
||||
# RECFLARE_DISCORD_BENEFITS_ROLE_IDS=1077000000000000001,1077000000000000002
|
||||
#
|
||||
# Also add https://<your domain>/claim to the app's Redirects in the Discord developer
|
||||
# portal, or the exchange is refused. Granting Plus takes effect on the player's NEXT
|
||||
# sign-in. `runx admin grant-plus` sets it directly, with no Discord involved.
|
||||
|
||||
@@ -44,6 +44,20 @@
|
||||
- Changesets manage versions and changelogs
|
||||
</architecture>
|
||||
|
||||
<game-clients>
|
||||
Supported client builds live in `SUPPORTED_GAME_VERSIONS`
|
||||
(`packages/domain/src/presence-db.ts`); `GAME_VERSION` is the default the stack targets.
|
||||
|
||||
- `20230414` — default, official. Manifest `7859140924515540835` (2023).
|
||||
- `20250718.01` — beta, official. Manifest `1151455856673601091`; reaches this server via
|
||||
the [patch-2025](https://github.com/recflare/patch-2025) patch.
|
||||
- `20250424.01`, `20231207`, `20230616` — alpha.
|
||||
|
||||
Builds are date-stamped (`YYYYMMDD[.NN]`) so they order as plain strings; several
|
||||
surfaces gate on "newer than `20230414`" (econ storefront catalog, `api` event tags,
|
||||
`rooms` featured rooms) rather than on an explicit list.
|
||||
</game-clients>
|
||||
|
||||
<code-style>
|
||||
- Use tabs for indentation, spaces for alignment
|
||||
- Type imports use `import type`
|
||||
@@ -169,6 +183,13 @@ inconsistency here without checking the client first.
|
||||
- The cheer's reply is `{ Success, Message }` — PascalCase, with `Message` NULL on success.
|
||||
That is NOT the lowercase `{ success, error: "" }` envelope the reports and warnings use;
|
||||
the two live side by side in the same worker and must not be unified.
|
||||
- A Message's `Data` (every `MessageReceived` frame) is a STRING on the wire, so a payload
|
||||
with structure to it goes in ESCAPED — `"Data": "{\"PlayerId\":\"205\"}"`, never a nested
|
||||
object. An object there does not degrade: the client's decoder rejects it outright
|
||||
(`expected:'String Begin Token', actual:'{'`) and loses the whole notification, not just
|
||||
the field. Bites the vote-to-kick message (`api`: `POST /api/PlayerReporting/v3/voteToKick`,
|
||||
whose `Data` carries `{ PlayerId, Response, GameSessionId }` — `PlayerId` a string inside
|
||||
it, as the reference relays it) and, the same way, a chat message's `Contents`.
|
||||
- Leaderboard `Rank` (`leaderboard`: `GetRanks`, `GetNearbyScores`, `GetPlayerRank`) is
|
||||
0-BASED — the client adds one before it draws, so a `Rank` of 1 shows in game as second
|
||||
place and the top of a board must be 0. Its own slice says the same: it asks for the first
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<img width="1063" height="409" alt="image" src="https://github.com/user-attachments/assets/521d5b11-fb93-4900-9158-71d51d2343ae" />
|
||||
|
||||

|
||||

|
||||
|
||||
RecFlare is a scalable implementation of RecNet — the Rec Room backend — built on
|
||||
Cloudflare Workers. It implements the network services the Rec Room client talks
|
||||
@@ -36,15 +36,24 @@ resulting in bugs getting fixed faster. I hope.
|
||||
|
||||
## Game client
|
||||
|
||||
See [RecFlare Client](https://github.com/djdevin/recflare-client)
|
||||
See [RecFlare Client](https://github.com/djdevin/recflare-client) for the official RecFlare build.
|
||||
|
||||
RecFlare is compatible with the
|
||||
[RecNet Plugin](https://github.com/djdevin/recnet-plugin) and the build of Rec
|
||||
Room with manifest `7859140924515540835` (around 2023). Other client or game versions may expect
|
||||
different endpoints and response shapes and are not supported.
|
||||
These game builds are supported:
|
||||
|
||||
| Build | Manifest | Support |
|
||||
| ------------- | --------------------- | --------------------------------------------------------------------------------------- |
|
||||
| `20230414` | `7859140924515540835` | **Default**, official — the 2023 build the rest of the stack targets |
|
||||
| `20250718.01` | `1151455856673601091` | **Beta**, official — use the [patch-2025](https://github.com/recflare/patch-2025) patch |
|
||||
| `20250424.01` | | Alpha |
|
||||
| `20231207` | | Alpha |
|
||||
| `20230616` | | Alpha |
|
||||
|
||||
Alpha builds get past the version check and largely work, but nothing else in the
|
||||
stack targets them, so expect protocol differences. Other client or game versions
|
||||
may expect different endpoints and response shapes and are not supported.
|
||||
|
||||
Generally speaking any client that effectively rewrites the nameserver with the
|
||||
right mods can be used with this server.
|
||||
right mods (see [RecNet Plugin](https://github.com/djdevin/recnet-plugin), [2025 patch](https://github.com/recflare/patch-2025)) can be used with this server.
|
||||
|
||||
## Services
|
||||
|
||||
@@ -58,9 +67,11 @@ Want to run it yourself? See [DEPLOYING.md](DEPLOYING.md)
|
||||
|
||||
### What year is this for?
|
||||
|
||||
This works with 2023 clients. It has been tested with manifest `7859140924515540835`. Other clients may not work.
|
||||
Most 2023 and 2025 clients. A few older builds work at
|
||||
alpha quality — see the table in the "Game client" section above. Other clients may not
|
||||
work.
|
||||
|
||||
See the "Client" section above for instructions on how to modify a client to connect to this server.
|
||||
See the "Game client" section above for instructions on how to modify a client to connect to this server.
|
||||
|
||||
### Can I run this locally on my PC?
|
||||
|
||||
@@ -70,11 +81,13 @@ See "Run the development microservices" above. It may be possible later as Wrang
|
||||
|
||||
### Can I use this to make my own server?
|
||||
|
||||
Yes, that's the point. Just set your custom domain and deploy it.
|
||||
Yes, that's the point. See [DEPLOYING.md](DEPLOYING.md)
|
||||
|
||||
### Is there an admin panel?
|
||||
|
||||
Not yet. But there could be.
|
||||
Yes, the server comes bundled with a simple web panel with more functionality being added.
|
||||
|
||||
There are also [CLI tools](CLI.md) you can use for admin tasks like granting roles.
|
||||
|
||||
### Can I copy this project and modify it?
|
||||
|
||||
@@ -89,15 +102,20 @@ keeps those in sync: `pnpm` workspaces share dependencies, `@repo/` packages
|
||||
share code, Turborepo runs build/test/lint with a single cached task graph, and
|
||||
cross-service changes land in one atomic commit.
|
||||
|
||||
This makes it easier to deploy the whole stack at once or a smaller selection
|
||||
of microservices to avoid downtime events.
|
||||
|
||||
## Credits
|
||||
|
||||
I started this after the official servers shut down, so I could only see the
|
||||
I started this soon after the official servers shut down when I saw there were
|
||||
only monolithic servers usually running on one server. I could only see the
|
||||
request shapes coming from the game client. I used many different projects as
|
||||
resources to get response shapes, logic examples, enums, etc. They all had
|
||||
missing pieces. Again, another reason to come together on one project and
|
||||
stop gatekeeping.
|
||||
|
||||
Unfortunately, they were all leaked code except for
|
||||
[CannedNet](https://github.com/CannedNet/CannedNet), [DorkNet](https://github.com/DorkSquadRR/DorkNet), and jordanparki7's postman
|
||||
collection of RecNet APIs which is gone for some reason. So I will not list the
|
||||
leaks publicly.
|
||||
- [CannedNet](https://github.com/CannedNet/CannedNet)
|
||||
- [DorkNet](https://github.com/DorkSquadRR/DorkNet)
|
||||
- jordanparki7's postman collection of RecNet APIs which is gone for some reason.
|
||||
- Leaked C# projects I won't list (for response shapes)
|
||||
- Claude and my [wire shapes skill](https://github.com/recflare/skills/blob/main/.claude/skills/wire-shapes/SKILL.md)
|
||||
|
||||
@@ -76,7 +76,7 @@ function unauthorized(c: Context<App>) {
|
||||
}
|
||||
|
||||
/** Username changes a fresh account starts with (until one has been consumed). */
|
||||
const DEFAULT_USERNAME_CHANGES = 1
|
||||
const DEFAULT_USERNAME_CHANGES = 3
|
||||
|
||||
/**
|
||||
* Username-change result envelope: `{ success, error, value }`, always HTTP 200.
|
||||
|
||||
@@ -181,7 +181,7 @@ describe('auth-gated endpoints', () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/parentalcontrol/me`, {
|
||||
headers: await bearer(),
|
||||
})
|
||||
expect(await res.json()).toEqual({ accountId: 42, disallowInAppPurchases: false })
|
||||
expect(await res.json()).toEqual({ accountId: 42, disallowInAppPurchases: true })
|
||||
})
|
||||
|
||||
test('GET /accountprivacysettings/:id echoes the id with the privacy flags', async () => {
|
||||
|
||||
+419
-20
@@ -23,8 +23,8 @@ import { getInventionAcquisitionCounts, getOwnedInventionIds } from '@repo/domai
|
||||
/**
|
||||
* Schema DDL (mirror of migrations/0002_invention.sql + 0003_invention_featured.sql +
|
||||
* 0008_invention_visibility.sql, sans any seed rows). `is_featured` backs the featured
|
||||
* feed's query and `is_published`/`hide_from_player` the "may anyone see this" filter
|
||||
* every feed shares; json_extract of a JSON `true` is 1, so those columns are 1/0 — and
|
||||
* feed's query and `is_published`/`hide_from_player` most of the "may anyone see this"
|
||||
* filter every feed shares (see `VISIBLE_IN_FEEDS`, which also excludes unlisted ones); json_extract of a JSON `true` is 1, so those columns are 1/0 — and
|
||||
* NULL when the key is missing, which fails a `= 1` or `= 0` test either way.
|
||||
*/
|
||||
export const SCHEMA_DDL: string[] = [
|
||||
@@ -53,6 +53,16 @@ export interface InventionVersion {
|
||||
ChipsCost: number
|
||||
CloudVariablesCost: number
|
||||
AICost: number
|
||||
/**
|
||||
* Whether the blob uses content still in beta, sent from `v9/save` on. It sits on the
|
||||
* VERSION, where the client's own `RRInventionVersion` carries it and for the same
|
||||
* reason the costs do: it describes the one revision saved, not the invention across
|
||||
* all of them. `UgcVersion`, which reads like its twin, is an INVENTION field — see
|
||||
* {@link SavedInvention}. Absent on a record saved through `v6/save`, which sends
|
||||
* neither; the client's decoder reads a missing member as its default, so an old
|
||||
* record is not retroactively wrong.
|
||||
*/
|
||||
HasBetaContent?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,9 +113,32 @@ export interface SavedInvention {
|
||||
HideFromPlayer: boolean
|
||||
ReferencedInventions: number[]
|
||||
/**
|
||||
* Tags served by `v1/details` and written by `v1/settags`. Optional and unset on
|
||||
* save: the real `RRInvention` carries no Tags field and the client sends no tags
|
||||
* when saving, so an untagged invention's DTO stays identical to the real one.
|
||||
* The rest of what `v9/save` sends, kept beside `ReferencedInventions` — the field
|
||||
* they most resemble, and the one this record has always carried on the invention.
|
||||
* Note the v9 client's own `RRInvention` has no `Referenced*` at all (its VERSION
|
||||
* carries them) and no `LongDescription`/`ConvertedFromInventionId` (it sends both and
|
||||
* never reads them back); they are stored anyway, because what the client sent is
|
||||
* worth keeping, and `toSaveResultV9` puts each where that client expects it.
|
||||
*
|
||||
* `UgcVersion` is the exception that has to be got right rather than tolerated: it is
|
||||
* an invention field there, not a version one, next to `CurrentVersionNumber`.
|
||||
*
|
||||
* `DisplayMetadataJson` is stored as the opaque string the client sent: it is the
|
||||
* client's own display state (`{"0":0,"99":0}`), and re-encoding it would be
|
||||
* inventing a schema for something only the client reads.
|
||||
*
|
||||
* Each is absent on a `v6/save` record, which sends none of them.
|
||||
*/
|
||||
ReferencedUnityAssetIds?: string[]
|
||||
UgcVersion?: number
|
||||
LongDescription?: string
|
||||
DisplayMetadataJson?: string
|
||||
ConvertedFromInventionId?: number
|
||||
/**
|
||||
* Tags served by `v1/details` and written by `v1/settags`. Optional: the real
|
||||
* `RRInvention` carries no Tags field, so an untagged invention's DTO stays
|
||||
* identical to the real one. `v6/save` never sets it (that client tags in a second
|
||||
* call); `v9/save` sets it when its `tagsRequest` names at least one tag.
|
||||
*/
|
||||
Tags?: InventionTag[]
|
||||
}
|
||||
@@ -130,6 +163,224 @@ export function toSaveResult(invention: SavedInvention): InventionSaveResult {
|
||||
return { Status: 0, Invention: invention, InventionVersion: invention.CurrentVersion }
|
||||
}
|
||||
|
||||
/**
|
||||
* `TagsResponse.Result` on a v9 save — the client's own tag-result enum, whose members
|
||||
* run Success 0 … ReservedWordViolation 13. Only Success is named: the members between
|
||||
* were not recovered from the client, and it never reads this field anyway, so a refused
|
||||
* tag needs only to be something other than Success.
|
||||
*/
|
||||
export const INVENTION_TAG_RESULT = {
|
||||
success: 0,
|
||||
rejected: 1,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* The `Invention` inside a v9 save response — the client's 28-key `RRInvention`, which is
|
||||
* NOT the record this server stores (that one mirrors the older shape the read endpoints
|
||||
* still serve). The differences that matter: no nested `CurrentVersion` (the version rides
|
||||
* beside it), no `Referenced*` (they moved onto the version), no `IsPublished` (the client
|
||||
* infers it from `FirstPublishedAt`), and `UgcVersion`/`LatestVersionNumber`/
|
||||
* `ForceCannotPublish`/`IsRecRoomApproved` that the stored record has no equivalent for.
|
||||
*
|
||||
* The client reads exactly one of these keys — `InventionId` — and its decoder null-checks
|
||||
* every member and drops the ones it doesn't know, so this projection is about being right
|
||||
* rather than about being parseable.
|
||||
*/
|
||||
export interface InventionV9Dto {
|
||||
InventionId: number
|
||||
ReplicationId: string
|
||||
CreatorPlayerId: number
|
||||
Name: string
|
||||
Description: string
|
||||
ImageName: string
|
||||
UgcVersion: number
|
||||
CurrentVersionNumber: number
|
||||
LatestVersionNumber: number
|
||||
Accessibility: number
|
||||
ForceCannotPublish: boolean
|
||||
ModifiedAt: string
|
||||
CreatedAt: string
|
||||
FirstPublishedAt: string | null
|
||||
CreationRoomId: number | null
|
||||
NumPlayersHaveUsedInRoom: number
|
||||
NumDownloads: number
|
||||
CheerCount: number
|
||||
CreatorPermission: number
|
||||
GeneralPermission: number
|
||||
IsAGInvention: boolean
|
||||
IsCertifiedInvention: boolean
|
||||
IsRecRoomApproved: boolean
|
||||
AllowTrial: boolean
|
||||
Price: number | null
|
||||
HideFromPlayer: boolean
|
||||
DisplayMetadataJson: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* The `InventionVersion` inside a v9 save response — the client's 13-key
|
||||
* `RRInventionVersion`. It carries `HasBetaContent`, a `CreatedAt` of its own and a
|
||||
* nullable `UgcAccessibility` the stored version has no field for, and notably NO
|
||||
* `AICost`, which the request body still sends and this server still stores.
|
||||
*
|
||||
* Both `Referenced*` lists are emitted here even though the client's DTO has room for one:
|
||||
* which of the two it is wasn't recovered, and an unknown member is dropped silently while
|
||||
* a missing one would be the list the client asked for going astray.
|
||||
*/
|
||||
export interface InventionVersionV9Dto {
|
||||
InventionId: number
|
||||
ReplicationId: string
|
||||
VersionNumber: number
|
||||
HasBetaContent: boolean
|
||||
InstantiationCost: number
|
||||
LightsCost: number
|
||||
ChipsCost: number
|
||||
CloudVariablesCost: number
|
||||
BlobName: string
|
||||
BlobHash: string | null
|
||||
CreatedAt: string
|
||||
UgcAccessibility: number | null
|
||||
ReferencedInventions: number[]
|
||||
ReferencedUnityAssetIds: string[]
|
||||
}
|
||||
|
||||
/** The tag half of a v9 save — `v1/settags`' answer, folded into the save response. */
|
||||
export interface InventionTagsV9Dto {
|
||||
Result: number
|
||||
Tags: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The four keys inside a v9 save envelope's `Value`. `Status` is 0 on success.
|
||||
*
|
||||
* `InventionVersion` and `TagsResponse` are nullable because the envelope is not the save
|
||||
* route's alone: econ's `POST /api/storefronts/v3/buyInvention` answers in it too, and a
|
||||
* BUY mints neither a version nor a tag result — it sends both as null, and the client
|
||||
* (which reads only `Success` and `Value.Invention`) never looks. The keys stay present.
|
||||
*/
|
||||
export interface InventionSaveV9Value {
|
||||
Status: number
|
||||
Invention: InventionV9Dto
|
||||
InventionVersion: InventionVersionV9Dto | null
|
||||
TagsResponse: InventionTagsV9Dto | null
|
||||
}
|
||||
|
||||
/**
|
||||
* What `v9/save` answers, and the whole reason it isn't just v6 with a bigger body: the
|
||||
* result is ENVELOPED, where v6 serves the bare `{ Status, Invention, InventionVersion }`.
|
||||
*
|
||||
* The client's contract is two fields deep. It checks `Success`, then reads
|
||||
* `Value.Invention.InventionId` and tags the invention with it; `Error` is the only text
|
||||
* that ever reaches a human (it is logged as "Invention datablob upload failed"). `Status`
|
||||
* is deserialized and never read on this route — the failure channel is the envelope, not
|
||||
* the 55-member status enum — and so are `InventionVersion` and `TagsResponse`.
|
||||
*
|
||||
* The one shape that CRASHES the client is `Success: true` with `Value` null or absent: it
|
||||
* dereferences `Value.Invention` unguarded. `Success: false` with a null `Value` is safe —
|
||||
* that branch reads only `Error` — which is why every refusal goes through
|
||||
* {@link inventionSaveV9Failure} rather than answering a bare `{ error }` like v6 does. A
|
||||
* body that doesn't deserialize into this envelope at all is the same crash, so even the
|
||||
* 401 answers it.
|
||||
*/
|
||||
export interface InventionSaveV9Result {
|
||||
Value: InventionSaveV9Value | null
|
||||
Success: boolean
|
||||
Error: string | null
|
||||
error_id: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Project a stored invention into the client's 28-key `RRInvention`. Shared by the v9 save
|
||||
* envelope below and by econ's `v3/buyInvention`, which answers in that same envelope — so
|
||||
* the two can never drift into serving one build two different inventions.
|
||||
*
|
||||
* Fields the stored record has no equivalent for are served as what they are here rather
|
||||
* than guessed: nothing forces an invention not to publish, and nothing in this server
|
||||
* approves one.
|
||||
*/
|
||||
export function toInventionV9(invention: SavedInvention): InventionV9Dto {
|
||||
return {
|
||||
InventionId: invention.InventionId,
|
||||
ReplicationId: invention.ReplicationId,
|
||||
CreatorPlayerId: invention.CreatorPlayerId,
|
||||
Name: invention.Name,
|
||||
Description: invention.Description,
|
||||
ImageName: invention.ImageName,
|
||||
UgcVersion: invention.UgcVersion ?? 0,
|
||||
CurrentVersionNumber: invention.CurrentVersionNumber,
|
||||
// One save, one version: the newest is the current one.
|
||||
LatestVersionNumber: invention.CurrentVersionNumber,
|
||||
Accessibility: invention.Accessibility,
|
||||
ForceCannotPublish: false,
|
||||
ModifiedAt: invention.ModifiedAt,
|
||||
CreatedAt: invention.CreatedAt,
|
||||
FirstPublishedAt: invention.FirstPublishedAt,
|
||||
CreationRoomId: invention.CreationRoomId,
|
||||
NumPlayersHaveUsedInRoom: invention.NumPlayersHaveUsedInRoom,
|
||||
NumDownloads: invention.NumDownloads,
|
||||
CheerCount: invention.CheerCount,
|
||||
CreatorPermission: invention.CreatorPermission,
|
||||
GeneralPermission: invention.GeneralPermission,
|
||||
IsAGInvention: invention.IsAGInvention,
|
||||
IsCertifiedInvention: invention.IsCertifiedInvention,
|
||||
IsRecRoomApproved: false,
|
||||
AllowTrial: invention.AllowTrial,
|
||||
Price: invention.Price,
|
||||
HideFromPlayer: invention.HideFromPlayer,
|
||||
DisplayMetadataJson: invention.DisplayMetadataJson ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Project a stored invention into the v9 save envelope. `tags` are the ones stored with
|
||||
* it, answered as the bare names `v1/settags` answers with; `tagResult` says whether they
|
||||
* were taken (see {@link INVENTION_TAG_RESULT}) — a tag the rules refuse costs the tags,
|
||||
* never the save, because the save is the thing the player would have to redo.
|
||||
*/
|
||||
export function toSaveResultV9(
|
||||
invention: SavedInvention,
|
||||
tags: InventionTag[],
|
||||
tagResult: number = INVENTION_TAG_RESULT.success
|
||||
): InventionSaveV9Result {
|
||||
const version = invention.CurrentVersion
|
||||
return {
|
||||
Value: {
|
||||
Status: 0,
|
||||
Invention: toInventionV9(invention),
|
||||
InventionVersion: {
|
||||
InventionId: version.InventionId,
|
||||
ReplicationId: version.ReplicationId,
|
||||
VersionNumber: version.VersionNumber,
|
||||
HasBetaContent: version.HasBetaContent ?? false,
|
||||
InstantiationCost: version.InstantiationCost,
|
||||
LightsCost: version.LightsCost,
|
||||
ChipsCost: version.ChipsCost,
|
||||
CloudVariablesCost: version.CloudVariablesCost,
|
||||
BlobName: version.BlobName,
|
||||
BlobHash: version.BlobHash,
|
||||
// The version is minted with the invention, so they share a timestamp.
|
||||
CreatedAt: invention.CreatedAt,
|
||||
UgcAccessibility: null,
|
||||
ReferencedInventions: invention.ReferencedInventions,
|
||||
ReferencedUnityAssetIds: invention.ReferencedUnityAssetIds ?? [],
|
||||
},
|
||||
TagsResponse: { Result: tagResult, Tags: tags.map((t) => t.Tag) },
|
||||
},
|
||||
Success: true,
|
||||
Error: null,
|
||||
error_id: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A refused v9 save. `Value` is null, which is safe precisely because `Success` is false:
|
||||
* the client reads `Error` on that branch and nothing else. See
|
||||
* {@link InventionSaveV9Result} for why the alternative — a bare `{ error }` body — would
|
||||
* take the client down instead.
|
||||
*/
|
||||
export function inventionSaveV9Failure(message: string): InventionSaveV9Result {
|
||||
return { Value: null, Success: false, Error: message, error_id: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Invention data blobs are named `<name>.inv`, and the client expects the extension
|
||||
* on the `BlobName` it reads back. Uploads through the `storage` worker already land
|
||||
@@ -189,6 +440,19 @@ export interface NewInvention {
|
||||
aiCost?: number
|
||||
creationRoomId?: number | null
|
||||
referencedInventions?: number[]
|
||||
/**
|
||||
* The rest of what `v9/save` sends. Every one is optional and is written onto the
|
||||
* record only when the caller actually supplied it, so a `v6/save` — which sends
|
||||
* none of them — stores and answers exactly the record it always did.
|
||||
*/
|
||||
ugcVersion?: number
|
||||
hasBetaContent?: boolean
|
||||
referencedUnityAssetIds?: string[]
|
||||
longDescription?: string | null
|
||||
displayMetadataJson?: string | null
|
||||
convertedFromInventionId?: number | null
|
||||
/** Tags to store with the record, already normalized by {@link normalizeInventionTags}. */
|
||||
tags?: InventionTag[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,6 +465,11 @@ export interface NewInvention {
|
||||
* what narrows `GeneralPermission` down (to UseOnly by default). Trials are allowed.
|
||||
* The client's `creatorAccountRole` is ignored: it's the player's role in the room
|
||||
* they built it in, not a permission over the invention.
|
||||
*
|
||||
* The fields `v9/save` added over `v6/save` are written only when the caller supplies
|
||||
* them, so the record a v6 client stores is byte-for-byte the one it always stored —
|
||||
* the new keys appear on new records rather than being back-filled with defaults onto
|
||||
* every old one.
|
||||
*/
|
||||
export async function createInvention(
|
||||
db: D1Database,
|
||||
@@ -233,6 +502,7 @@ export async function createInvention(
|
||||
ChipsCost: input.chipsCost ?? 0,
|
||||
CloudVariablesCost: input.cloudVariablesCost ?? 0,
|
||||
AICost: input.aiCost ?? 0,
|
||||
...(input.hasBetaContent === undefined ? {} : { HasBetaContent: input.hasBetaContent }),
|
||||
},
|
||||
Accessibility: 0,
|
||||
IsPublished: false,
|
||||
@@ -252,6 +522,16 @@ export async function createInvention(
|
||||
AllowTrial: true,
|
||||
HideFromPlayer: false,
|
||||
ReferencedInventions: input.referencedInventions ?? [],
|
||||
...(input.referencedUnityAssetIds === undefined
|
||||
? {}
|
||||
: { ReferencedUnityAssetIds: input.referencedUnityAssetIds }),
|
||||
...(input.ugcVersion === undefined ? {} : { UgcVersion: input.ugcVersion }),
|
||||
...(input.longDescription ? { LongDescription: input.longDescription } : {}),
|
||||
...(input.displayMetadataJson ? { DisplayMetadataJson: input.displayMetadataJson } : {}),
|
||||
...(typeof input.convertedFromInventionId === 'number'
|
||||
? { ConvertedFromInventionId: input.convertedFromInventionId }
|
||||
: {}),
|
||||
...(input.tags?.length ? { Tags: input.tags } : {}),
|
||||
}
|
||||
await db.prepare('INSERT INTO invention (data) VALUES (?1)').bind(JSON.stringify(invention)).run()
|
||||
return invention
|
||||
@@ -379,7 +659,7 @@ export async function searchInventions(
|
||||
const offset = Math.max(skip, 0)
|
||||
if (limit === 0) return []
|
||||
|
||||
const where = ['is_published = 1', 'hide_from_player = 0']
|
||||
const where = [...VISIBLE_IN_FEEDS]
|
||||
const binds: Array<string | number> = []
|
||||
/** Bind a value and get its placeholder, so the numbering can't drift as terms are added. */
|
||||
const bind = (v: string | number): string => `?${binds.push(v)}`
|
||||
@@ -421,8 +701,7 @@ async function publicInventions(db: D1Database, featuredOnly = false): Promise<S
|
||||
const { results } = await db
|
||||
.prepare(
|
||||
`SELECT data FROM invention
|
||||
WHERE is_published = 1
|
||||
AND hide_from_player = 0
|
||||
WHERE ${VISIBLE_IN_FEEDS.join(' AND ')}
|
||||
${featuredOnly ? 'AND is_featured = 1' : ''}`
|
||||
)
|
||||
.all<InventionRow>()
|
||||
@@ -499,10 +778,9 @@ export async function getFeaturedInventions(
|
||||
/**
|
||||
* Replace an invention's tags (the `v1/settags` write). Auto tags are the ones the
|
||||
* client derives from the invention itself (Type 2); custom tags are the creator's
|
||||
* own (Type 0). Both lists are replaced wholesale — auto first, then custom, the
|
||||
* order the tags come back in — and are lowercased/trimmed and de-duplicated so
|
||||
* `details` doesn't echo back near-duplicates. Returns the stored tag list, or null
|
||||
* when there's no such invention.
|
||||
* own (Type 0). Both lists are replaced wholesale, normalized as
|
||||
* {@link normalizeInventionTags} describes. Returns the stored tag list, or null when
|
||||
* there's no such invention.
|
||||
*/
|
||||
export async function setInventionTags(
|
||||
db: D1Database,
|
||||
@@ -513,6 +791,21 @@ export async function setInventionTags(
|
||||
const invention = await getInventionById(db, inventionId)
|
||||
if (invention === null) return null
|
||||
|
||||
const tags = normalizeInventionTags(autoTags, customTags)
|
||||
await writeInvention(db, { ...invention, Tags: tags })
|
||||
return tags
|
||||
}
|
||||
|
||||
/**
|
||||
* The two tag lists as they are stored: auto first (Type 2), then custom (Type 0) —
|
||||
* the order they come back in — each trimmed, lowercased and de-duplicated across both
|
||||
* lists so `details` doesn't echo back near-duplicates. Blanks are dropped: the client
|
||||
* pads its lists with empties.
|
||||
*
|
||||
* Shared by `v1/settags` and by `v9/save`, which carries the same two lists in its
|
||||
* `tagsRequest` — a tag has to mean the same thing however it arrived.
|
||||
*/
|
||||
export function normalizeInventionTags(autoTags: string[], customTags: string[]): InventionTag[] {
|
||||
const tags: InventionTag[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const [list, type] of [
|
||||
@@ -526,8 +819,6 @@ export async function setInventionTags(
|
||||
tags.push({ Tag: tag, Type: type })
|
||||
}
|
||||
}
|
||||
|
||||
await writeInvention(db, { ...invention, Tags: tags })
|
||||
return tags
|
||||
}
|
||||
|
||||
@@ -540,6 +831,9 @@ export async function setInventionTags(
|
||||
export const INVENTION_PERMISSION = {
|
||||
unassigned: 0,
|
||||
limitedoneuseonly: 10,
|
||||
// Recovered from the client's own ladder; nothing here sends it, and no name for it
|
||||
// appears in `v1/update`'s picker.
|
||||
disallowkeylock: 15,
|
||||
useonly: 20,
|
||||
editandsave: 40,
|
||||
publish: 60,
|
||||
@@ -547,6 +841,36 @@ export const INVENTION_PERMISSION = {
|
||||
unlimited: 100,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Where a published invention may be FOUND, which `v4/publish` sets and nothing before it
|
||||
* did — every record written before that endpoint carries 0, the value a save mints.
|
||||
*
|
||||
* Only `unlisted` is recovered from the client for certain; the other two mirror the room
|
||||
* accessibility enum, which they match member-for-member, and the publish sheet sends 1 for
|
||||
* an ordinary publish.
|
||||
*
|
||||
* Note what that leaves ambiguous: a stored 0 is either "private" or "written before this
|
||||
* enum meant anything", and the two are indistinguishable without a backfill. So the browse
|
||||
* filter excludes `unlisted` by name rather than requiring `public` — the latter reads
|
||||
* every invention published through `v3/publish` as private and empties the feeds.
|
||||
*/
|
||||
export const INVENTION_ACCESSIBILITY = {
|
||||
private: 0,
|
||||
public: 1,
|
||||
unlisted: 2,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* The "anyone may come across this" test the browse feeds and search share: published, not
|
||||
* hidden, and not unlisted. An unlisted invention is still reachable BY ID — that is what
|
||||
* unlisted means — so the by-id reads deliberately don't apply it.
|
||||
*/
|
||||
const VISIBLE_IN_FEEDS = [
|
||||
'is_published = 1',
|
||||
'hide_from_player = 0',
|
||||
`COALESCE(json_extract(data, '$.Accessibility'), 0) <> ${INVENTION_ACCESSIBILITY.unlisted}`,
|
||||
]
|
||||
|
||||
/**
|
||||
* Parse a permission level the way the client sends it: a name (`useonly`,
|
||||
* `edit_and_save`) or the raw number. Undefined when it's neither.
|
||||
@@ -567,6 +891,13 @@ export interface InventionPatch {
|
||||
imageName?: string
|
||||
allowTrial?: boolean
|
||||
generalPermission?: number
|
||||
/**
|
||||
* The rest of what `v2/metadata` can edit. Undefined leaves the stored value alone,
|
||||
* which is how both editors say "not this field" — `v1/update` by omitting the query
|
||||
* param, `v2/metadata` by sending the key as null.
|
||||
*/
|
||||
longDescription?: string
|
||||
tags?: InventionTag[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -591,11 +922,27 @@ export async function updateInvention(
|
||||
ImageName: patch.imageName ?? invention.ImageName,
|
||||
AllowTrial: patch.allowTrial ?? invention.AllowTrial,
|
||||
GeneralPermission: patch.generalPermission ?? invention.GeneralPermission,
|
||||
// Both of these are optional ON the record, so an untouched one resolves to
|
||||
// undefined and JSON.stringify drops the key — an invention that never had a long
|
||||
// description doesn't acquire an empty one by being edited.
|
||||
LongDescription: patch.longDescription ?? invention.LongDescription,
|
||||
Tags: patch.tags ?? invention.Tags,
|
||||
}
|
||||
await writeInvention(db, updated)
|
||||
return updated
|
||||
}
|
||||
|
||||
/**
|
||||
* What a publish decides. Each is optional and an omitted one keeps what the invention
|
||||
* has — except the permission, which falls back to UseOnly, the level the older
|
||||
* `v3/publish` has always defaulted to when its query string named none.
|
||||
*/
|
||||
export interface InventionPublish {
|
||||
permissionLevel?: number
|
||||
accessibility?: number
|
||||
price?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish an invention (`v3/publish`) — what puts it into search and the feeds.
|
||||
* Publishing sets the permission other players get (UseOnly unless the creator asks
|
||||
@@ -605,8 +952,7 @@ export async function updateInvention(
|
||||
export async function publishInvention(
|
||||
db: D1Database,
|
||||
inventionId: number,
|
||||
permissionLevel: number | undefined,
|
||||
price: number | undefined
|
||||
publish: InventionPublish = {}
|
||||
): Promise<SavedInvention | null> {
|
||||
const invention = await getInventionById(db, inventionId)
|
||||
if (invention === null) return null
|
||||
@@ -614,8 +960,13 @@ export async function publishInvention(
|
||||
const updated: SavedInvention = {
|
||||
...invention,
|
||||
IsPublished: true,
|
||||
GeneralPermission: permissionLevel ?? INVENTION_PERMISSION.useonly,
|
||||
Price: price ?? 0,
|
||||
GeneralPermission: publish.permissionLevel ?? INVENTION_PERMISSION.useonly,
|
||||
Accessibility: publish.accessibility ?? invention.Accessibility,
|
||||
// An unmentioned price is the price it already has, not zero: a republish that says
|
||||
// nothing about money must not quietly give away something that was for sale. A
|
||||
// first publish is unaffected — a fresh invention's price is 0 either way.
|
||||
Price: publish.price ?? invention.Price,
|
||||
// The FIRST publish is the one that gets dated; re-publishing doesn't reset it.
|
||||
FirstPublishedAt: invention.FirstPublishedAt ?? new Date().toISOString(),
|
||||
}
|
||||
await writeInvention(db, updated)
|
||||
@@ -638,6 +989,55 @@ export async function setInventionPrice(
|
||||
return updated
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an invention (`v2/delete`), returning the record that was removed, or null
|
||||
* when there's no such row. The whole invention lives in the one JSON blob, so its
|
||||
* versions, tags and referenced-invention lists go with it in a single DELETE.
|
||||
*
|
||||
* Two things are deliberately LEFT behind.
|
||||
*
|
||||
* The data blob in R2 stays: it is named by the file the creator uploaded through the
|
||||
* `storage` worker, and nothing here knows whether another record still points at that
|
||||
* name (a converted invention carries the same lineage, and a save that reuses a
|
||||
* filename reuses the object). An orphan blob costs storage; a missing one breaks
|
||||
* whatever still references it.
|
||||
*
|
||||
* The `inventory_invention` rows stay too — deleting a creator's invention must not
|
||||
* rewrite what other players bought. They already fall out of every list on their own:
|
||||
* `getMyInventions` resolves owned ids against this table and an id with no row left
|
||||
* simply drops out, and `ownsAllInventions` reads a missing row as not-owned. Purging
|
||||
* them would also erase the acquisition history that ranks the "top today" feed.
|
||||
*/
|
||||
export async function deleteInvention(
|
||||
db: D1Database,
|
||||
inventionId: number
|
||||
): Promise<SavedInvention | null> {
|
||||
const invention = await getInventionById(db, inventionId)
|
||||
if (invention === null) return null
|
||||
await db.prepare('DELETE FROM invention WHERE id = ?1').bind(inventionId).run()
|
||||
return invention
|
||||
}
|
||||
|
||||
/**
|
||||
* What `v2/delete` answers: the same `{ Value, Success, Error, error_id }` envelope the
|
||||
* other newer-client invention routes use, with `Value` always NULL — the invention is
|
||||
* gone, so there is nothing for the client to redraw from and it reads only `Success`
|
||||
* (and `Error`, the one string that reaches a human). This is why the delete does not
|
||||
* borrow {@link InventionSaveV9Result}: that envelope's `Value` carries an invention the
|
||||
* client dereferences, and a delete has none to give.
|
||||
*/
|
||||
export interface InventionDeleteResult {
|
||||
Value: null
|
||||
Success: boolean
|
||||
Error: string | null
|
||||
error_id: string | null
|
||||
}
|
||||
|
||||
/** The delete envelope: a refusal when given a message, success when given null. */
|
||||
export function inventionDeleteResult(error: string | null = null): InventionDeleteResult {
|
||||
return { Value: null, Success: error === null, Error: error, error_id: null }
|
||||
}
|
||||
|
||||
/** The tag filter chips the client offers when browsing inventions. */
|
||||
export interface InventionTagFilters {
|
||||
PinnedFilters: string[]
|
||||
@@ -714,8 +1114,7 @@ export async function getInventionsByRoom(
|
||||
.prepare(
|
||||
`SELECT data FROM invention
|
||||
WHERE json_extract(data, '$.CreationRoomId') = ?1
|
||||
AND is_published = 1
|
||||
AND hide_from_player = 0`
|
||||
AND ${VISIBLE_IN_FEEDS.join(' AND ')}`
|
||||
)
|
||||
.bind(roomId)
|
||||
.all<InventionRow>()
|
||||
|
||||
+290
-19
@@ -119,12 +119,14 @@ export const SuccessErrorEnvelope = z.object({
|
||||
|
||||
// ---- Config ----------------------------------------------------------------
|
||||
|
||||
/** `GET /api/config/v1/amplitude` — analytics keys (all disabled on this server). */
|
||||
/** `GET /api/config/v1/amplitude` — the client's analytics keys (blank; RudderStack and StatSig off). */
|
||||
export const AmplitudeConfig = z.object({
|
||||
AmplitudeKey: z.string(),
|
||||
StatSigKey: z.string(),
|
||||
RudderStackKey: z.string(),
|
||||
UseRudderStack: z.boolean(),
|
||||
RudderStackKey: z.string(),
|
||||
UseStatSig: z.boolean(),
|
||||
StatSigKey: z.string(),
|
||||
StatSigEnvironment: z.number().int(),
|
||||
})
|
||||
|
||||
/** `GET /api/config/v1/azurespeech` — speech-to-text config; `Enabled` is false here. */
|
||||
@@ -420,6 +422,10 @@ export const InventionVersionDto = z.object({
|
||||
ChipsCost: z.int(),
|
||||
CloudVariablesCost: z.int(),
|
||||
AICost: z.int(),
|
||||
HasBetaContent: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Set from `v9/save` on — absent on a version saved through `v6/save`'),
|
||||
})
|
||||
|
||||
/** A tag on an invention. `Type` 0 = custom (creator-submitted), 2 = auto-derived. */
|
||||
@@ -456,10 +462,30 @@ export const InventionDto = z.object({
|
||||
AllowTrial: z.boolean(),
|
||||
HideFromPlayer: z.boolean(),
|
||||
ReferencedInventions: z.array(z.int()),
|
||||
ReferencedUnityAssetIds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Set from `v9/save` on — absent on an invention saved through `v6/save`'),
|
||||
UgcVersion: z
|
||||
.int()
|
||||
.optional()
|
||||
.describe('An invention field, not a version one — set from `v9/save` on'),
|
||||
LongDescription: z.string().optional().describe('Set from `v9/save` on, when non-empty'),
|
||||
DisplayMetadataJson: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('The client’s own display state, stored as the opaque string it sent'),
|
||||
ConvertedFromInventionId: z
|
||||
.int()
|
||||
.optional()
|
||||
.describe('The invention this one was converted from, when `v9/save` named one'),
|
||||
Tags: z
|
||||
.array(InventionTagDto)
|
||||
.optional()
|
||||
.describe('Unset on save — the real RRInvention carries no Tags field'),
|
||||
.describe(
|
||||
'The real RRInvention carries no Tags field. Unset by `v6/save`; set by `v9/save` ' +
|
||||
'when its `tagsRequest` names at least one tag'
|
||||
),
|
||||
})
|
||||
|
||||
/** The `{ Status, Invention, InventionVersion }` envelope every invention write answers. */
|
||||
@@ -469,6 +495,88 @@ export const InventionSaveResult = z.object({
|
||||
InventionVersion: InventionVersionDto,
|
||||
})
|
||||
|
||||
/**
|
||||
* The `Invention` a v9 save answers with — the newer client's own `RRInvention`, which is
|
||||
* not the record this server stores or the read endpoints serve: no nested
|
||||
* `CurrentVersion` (the version rides beside it), no `Referenced*` (those moved onto the
|
||||
* version), no `IsPublished`.
|
||||
*/
|
||||
export const InventionV9Dto = z.object({
|
||||
InventionId: z.int(),
|
||||
ReplicationId: z.string(),
|
||||
CreatorPlayerId: z.int(),
|
||||
Name: z.string(),
|
||||
Description: z.string(),
|
||||
ImageName: z.string(),
|
||||
UgcVersion: z.int().describe('The UGC format the blob was written in; 0 when unsent'),
|
||||
CurrentVersionNumber: z.int(),
|
||||
LatestVersionNumber: z.int().describe('The same as CurrentVersionNumber on a fresh save'),
|
||||
Accessibility: z.int(),
|
||||
ForceCannotPublish: z.boolean().describe('Always false — nothing here forbids publishing'),
|
||||
ModifiedAt: z.string(),
|
||||
CreatedAt: z.string(),
|
||||
FirstPublishedAt: z.string().nullable(),
|
||||
CreationRoomId: z.int().nullable(),
|
||||
NumPlayersHaveUsedInRoom: z.int(),
|
||||
NumDownloads: z.int(),
|
||||
CheerCount: z.int(),
|
||||
CreatorPermission: z.int(),
|
||||
GeneralPermission: z.int(),
|
||||
IsAGInvention: z.boolean(),
|
||||
IsCertifiedInvention: z.boolean(),
|
||||
IsRecRoomApproved: z.boolean().describe('Always false — nothing here approves an invention'),
|
||||
AllowTrial: z.boolean(),
|
||||
Price: z.int().nullable(),
|
||||
HideFromPlayer: z.boolean(),
|
||||
DisplayMetadataJson: z.string().nullable(),
|
||||
})
|
||||
|
||||
/**
|
||||
* The `InventionVersion` a v9 save answers with. It carries `HasBetaContent`, a `CreatedAt`
|
||||
* of its own and a nullable `UgcAccessibility`, and notably no `AICost` — which the request
|
||||
* still sends and this server still stores.
|
||||
*/
|
||||
export const InventionVersionV9Dto = z.object({
|
||||
InventionId: z.int(),
|
||||
ReplicationId: z.string(),
|
||||
VersionNumber: z.int(),
|
||||
HasBetaContent: z.boolean(),
|
||||
InstantiationCost: z.int(),
|
||||
LightsCost: z.int(),
|
||||
ChipsCost: z.int(),
|
||||
CloudVariablesCost: z.int(),
|
||||
BlobName: z.string(),
|
||||
BlobHash: z.string().nullable(),
|
||||
CreatedAt: z.string(),
|
||||
UgcAccessibility: z.int().nullable().describe('Always null — versions carry no accessibility'),
|
||||
ReferencedInventions: z.array(z.int()),
|
||||
ReferencedUnityAssetIds: z.array(z.string()),
|
||||
})
|
||||
|
||||
/**
|
||||
* What `v9/save` answers — the enveloped result. The client checks `Success` and then reads
|
||||
* `Value.Invention.InventionId`; `Error` is the only text it shows a human, and `Status`,
|
||||
* `InventionVersion` and `TagsResponse` are deserialized and never read. `Success: true`
|
||||
* with a null `Value` crashes it, so a refusal is `Success: false` with `Value: null`.
|
||||
*/
|
||||
export const InventionSaveV9Result = z.object({
|
||||
Value: z
|
||||
.object({
|
||||
Status: z.int().describe('0 = success; the client never reads it on this route'),
|
||||
Invention: InventionV9Dto,
|
||||
InventionVersion: InventionVersionV9Dto,
|
||||
TagsResponse: z.object({
|
||||
Result: z.int().describe('0 = success; non-zero when a tag broke the tag rule'),
|
||||
Tags: z.array(z.string()).describe('The stored tag NAMES, auto first, then custom'),
|
||||
}),
|
||||
})
|
||||
.nullable()
|
||||
.describe('Null when Success is false — and only then'),
|
||||
Success: z.boolean(),
|
||||
Error: z.string().nullable().describe('The refusal message; the only text the client shows'),
|
||||
error_id: z.string().nullable().describe('Always null'),
|
||||
})
|
||||
|
||||
/** The tag filter chips on a browse screen, derived from the tags actually in use. */
|
||||
export const TagFilters = z.object({
|
||||
PinnedFilters: z.array(z.string()),
|
||||
@@ -506,6 +614,76 @@ export const SetTagsResponse = z.object({
|
||||
Tags: z.array(z.string()).describe('Auto tags first, then custom'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `PUT /api/inventions/v2/metadata` JSON body — PascalCase, and every field but the id is
|
||||
* NULLABLE: the newer client sends the whole shape on every edit and marks the fields it
|
||||
* isn't touching as null. An empty string is not a null — it clears the field.
|
||||
*/
|
||||
export const UpdateInventionMetadataRequest = z.object({
|
||||
InventionId: z.int(),
|
||||
Name: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('3–24 chars, letters/digits/spaces/dashes/colons; null leaves it alone'),
|
||||
Description: z.string().nullable().optional().describe('Max 512 chars; empty clears it'),
|
||||
LongDescription: z.string().nullable().optional().describe('Empty clears it'),
|
||||
ImageName: z.string().nullable().optional().describe('New thumbnail; empty clears it'),
|
||||
TagsRequest: z
|
||||
.object({
|
||||
AutoTags: z.array(z.string()).nullable().optional(),
|
||||
CustomTags: z.array(z.string()).nullable().optional(),
|
||||
})
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Replaces both lists wholesale, as `v1/settags` does; null leaves them alone'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `POST /api/inventions/v4/publish` JSON body — PascalCase, and nullable the way
|
||||
* `v2/metadata`'s is: a null field keeps what the invention already has.
|
||||
*/
|
||||
export const PublishInventionRequest = z.object({
|
||||
InventionId: z.int(),
|
||||
Permission: z
|
||||
.int()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe(
|
||||
'The `GeneralPermission` other players get, as a raw ladder number: Unassigned 0, ' +
|
||||
'LimitedOneUseOnly 10, DisallowKeyLock 15, UseOnly 20, EditAndSave 40, Publish 60, ' +
|
||||
'Charge 80, Unlimited 100. Null publishes as UseOnly'
|
||||
),
|
||||
Accessibility: z
|
||||
.int()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Private 0, Public 1, Unlisted 2. Unlisted stays out of browse and search'),
|
||||
Price: z
|
||||
.int()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe('Price in tokens; null leaves it as it is, and a negative one is ignored'),
|
||||
})
|
||||
|
||||
/** `POST /api/inventions/v2/delete` JSON body — the id and nothing else. */
|
||||
export const DeleteInventionRequest = z.object({
|
||||
InventionId: z.int().describe('The invention to delete; the caller must have created it'),
|
||||
})
|
||||
|
||||
/**
|
||||
* What `v2/delete` answers — the same `{ Value, Success, Error, error_id }` envelope the
|
||||
* other newer-client invention routes use, with `Value` always null. The invention is
|
||||
* gone, so there is nothing for the client to redraw from: it reads `Success`, and
|
||||
* `Error` when that is false.
|
||||
*/
|
||||
export const InventionDeleteResult = z.object({
|
||||
Value: z.null().describe('Always null — the invention no longer exists'),
|
||||
Success: z.boolean(),
|
||||
Error: z.string().nullable().describe('The refusal message; null on success'),
|
||||
error_id: z.string().nullable().describe('Always null'),
|
||||
})
|
||||
|
||||
/** `POST /api/inventions/v1/updateprice` JSON body. */
|
||||
export const UpdatePriceRequest = z.object({
|
||||
InventionId: z.int(),
|
||||
@@ -533,6 +711,34 @@ export const SaveInventionRequest = z.object({
|
||||
aiCost: z.int().optional(),
|
||||
creationRoomId: z.int().optional(),
|
||||
referencedInventions: z.array(z.int()).optional(),
|
||||
creatorAccountRole: z
|
||||
.int()
|
||||
.optional()
|
||||
.describe('Accepted and ignored — a room role, not a permission over the invention'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `POST /api/inventions/v9/save` JSON body — `v6`’s fields plus what the invention
|
||||
* points at, what it says about itself, and the tags that used to need a second
|
||||
* `v1/settags` call.
|
||||
*/
|
||||
export const SaveInventionV9Request = SaveInventionRequest.extend({
|
||||
ugcVersion: z.int().optional().describe('The UGC format the blob was written in'),
|
||||
hasBetaContent: z.boolean().optional(),
|
||||
referencedUnityAssetIds: z.array(z.string()).optional(),
|
||||
longDescription: z.string().optional().describe('Stored when non-empty'),
|
||||
displayMetadataJson: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Opaque client display state, e.g. `{"0":0,"99":0}`; stored verbatim'),
|
||||
convertedFromInventionId: z.int().nullable().optional(),
|
||||
tagsRequest: z
|
||||
.object({
|
||||
AutoTags: z.array(z.string()).nullable().optional(),
|
||||
CustomTags: z.array(z.string()).nullable().optional(),
|
||||
})
|
||||
.optional()
|
||||
.describe('The same two lists `v1/settags` takes, folded into the save'),
|
||||
})
|
||||
|
||||
// ---- Avatar / custom avatar items ------------------------------------------
|
||||
@@ -1036,24 +1242,62 @@ export const VoteToKickReason = z.object({
|
||||
})
|
||||
|
||||
/**
|
||||
* `GET|POST /api/PlayerReporting/v1/moderationBlockDetails` — always the "not blocked"
|
||||
* answer (no ban storage yet), mirroring the reference server's stub
|
||||
* `ReturnModerationBlockDetails()`. `ReportCategory` is `Unknown` (-1) rather than 0,
|
||||
* which is a real category, and `Message` is null — the client distinguishes "no
|
||||
* message" from a blank one, so we send null where the reference sends an empty string.
|
||||
* `IsVoiceModAutoban`/`TimeoutStartedAt` are on the DTO but unset by that stub, so
|
||||
* they carry their C# defaults (false / null).
|
||||
* `GET|POST /api/PlayerReporting/v1/moderationBlockDetails` — the caller's block. With an
|
||||
* account-wide ban in force (a `report` row with `banned` set) it describes that ban:
|
||||
* `IsBan` true, the report's `ReportCategory`, a fixed `Message` of "Rule violation", and
|
||||
* its span as `TimeoutStartedAt` (the report's `created_at`) plus `Duration` (seconds to
|
||||
* `ban_expires`; int32 max for a permanent ban). Otherwise it is the "not blocked" answer, mirroring the reference server's stub `ReturnModerationBlockDetails()`:
|
||||
* `ReportCategory` is `Unknown` (-1) rather than 0, which is a real category, and
|
||||
* `Message` is null — the client distinguishes "no message" from a blank one, so we send
|
||||
* null where the reference sends an empty string. `IsVoiceModAutoban`/`TimeoutStartedAt`
|
||||
* are on the DTO but unset by that stub, so they carry their C# defaults (false / null).
|
||||
*
|
||||
* Sixteen keys on the wire — every one the 2025 client's `ModerationBlockDetail` formatter
|
||||
* reads. The seven past the stub's nine (`IsDeviceBan` … `BottomMessageOverride`) are
|
||||
* block kinds and screen dressings this server never hands out, so they always carry their
|
||||
* "none" value; they are sent so a decoder that wants the key present finds it.
|
||||
*/
|
||||
export const ModerationBlockDetails = z.object({
|
||||
ReportCategory: z.int().describe('-1 = ReportCategory.Unknown (0 is a real category)'),
|
||||
Duration: z.int(),
|
||||
ReportCategory: z
|
||||
.int()
|
||||
.describe(
|
||||
'The category the ban’s report was filed under; -1 = ReportCategory.Unknown when not blocked (0 is a real category)'
|
||||
),
|
||||
Duration: z
|
||||
.int()
|
||||
.describe(
|
||||
'Length of the block in seconds from `TimeoutStartedAt`; 2147483647 (int32 max) for a permanent ban; 0 when not blocked'
|
||||
),
|
||||
GameSessionId: z.int(),
|
||||
IsBan: z.boolean(),
|
||||
IsHostKick: z.boolean(),
|
||||
IsVoiceModAutoban: z.boolean(),
|
||||
Message: z.string().nullable(),
|
||||
PlayerIdReporter: z.int().nullable(),
|
||||
TimeoutStartedAt: z.string().nullable(),
|
||||
IsHostKick: z.boolean().describe('Always false — no host kick is ever recorded here'),
|
||||
Message: z.string().nullable().describe('“Rule violation” on a ban; null when not blocked'),
|
||||
PlayerIdReporter: z
|
||||
.int()
|
||||
.nullable()
|
||||
.describe('Always null — the reporter is not shown to the reported'),
|
||||
IsBan: z.boolean().describe('True when an account-wide ban is in force'),
|
||||
IsVoiceModAutoban: z.boolean().describe('Always false'),
|
||||
IsDeviceBan: z.boolean().describe('Always false — bans here are account-wide, not per device'),
|
||||
IsWarning: z
|
||||
.boolean()
|
||||
.describe('Always false — warnings are delivered as notifications, not here'),
|
||||
VoteKickReason: z.string().nullable().describe('Always null — no vote-kick is recorded here'),
|
||||
TimeoutStartedAt: z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe(
|
||||
'When the block began — the ban’s report `created_at` (ISO-8601 UTC); `Duration` runs from it. Null when not blocked'
|
||||
),
|
||||
AssociatedAccountUsername: z.string().nullable().describe('Always null'),
|
||||
ShowCreatorCodeOfConduct: z.boolean().describe('Always false'),
|
||||
TopMessageOverride: z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe('Always null — the client’s default block-screen text stands'),
|
||||
BottomMessageOverride: z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe('Always null — the client’s default block-screen text stands'),
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -1098,6 +1342,33 @@ export const CreateWarningRequest = z.object({
|
||||
ModeratorNote: z.string().optional().describe('Internal note; never shown to the player'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `POST /api/PlayerReporting/v3/voteToKick` form body — a player calling a vote on
|
||||
* another. Everything is a string on the wire (it's form-encoded). `Reason` is one of the
|
||||
* labels `GET /api/PlayerReporting/v1/voteToKickReasons` serves; the voter is NOT in the
|
||||
* body — it's the bearer token's subject.
|
||||
*/
|
||||
export const VoteToKickRequest = z.object({
|
||||
PlayerId: z.string().describe('Account id of the player being voted on'),
|
||||
Response: z.string().describe('The caller’s own vote, e.g. `True`'),
|
||||
Reason: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('A `voteToKickReasons` label, e.g. `Inactive in games (AFK)`'),
|
||||
GameSessionId: z.string().describe('The room instance both players are standing in'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `POST /api/PlayerReporting/v1/instantKick` JSON body — the players a room's staff are
|
||||
* ejecting from one live instance. JSON, not a form, unlike its neighbours in this
|
||||
* controller. `GameSessionId` is the room INSTANCE id (`roomInstanceId`); the kick is
|
||||
* scoped to it, so a player named here who is standing somewhere else is left alone.
|
||||
*/
|
||||
export const InstantKickRequest = z.object({
|
||||
GameSessionId: z.int().describe('The room instance (game session) to eject them from'),
|
||||
PlayerIds: z.array(z.int()).describe('Account ids to kick out of that instance'),
|
||||
})
|
||||
|
||||
/** `POST /api/PlayerReporting/v1/deviceId` form body — the id rotation the client reports. */
|
||||
export const DeviceIdRequest = z.object({
|
||||
oldDeviceId: z.string().optional().describe('The id the client thinks we hold'),
|
||||
|
||||
+11
-11
@@ -20,14 +20,14 @@
|
||||
* one polymorphic id because the keys differ in TYPE: two numbers and a guid.
|
||||
*
|
||||
* A report is also where an ACCOUNT-WIDE ban lives: acting on a report sets `banned`
|
||||
* on that same row (see `banFromReport`), so the ban carries the evidence for it. Two
|
||||
* workers read it — `match` refuses every matchmake for a banned player, and `auth`
|
||||
* refuses to issue them a token at all — both via `isPlayerBanned`. This is distinct
|
||||
* from the per-room `room_ban` table the rooms worker owns: that one keeps a player
|
||||
* out of ONE room, this one out of the game.
|
||||
*
|
||||
* `/api/PlayerReporting/v1/moderationBlockDetails` is NOT wired to it yet and still
|
||||
* answers "not blocked" unconditionally.
|
||||
* on that same row (see `banFromReport`), so the ban carries the evidence for it. It is
|
||||
* ENFORCED by `match`, which refuses every matchmake for a banned player, and DESCRIBED
|
||||
* by `/api/PlayerReporting/v1/moderationBlockDetails`, which tells the banned player why
|
||||
* (via `getActiveBan`). `auth` still issues a banned account a token — that is what lets
|
||||
* the client reach the block screen — and reads this table only for ban EVASION (an
|
||||
* account sharing a device or network with a banned one; see bans-db). This is distinct
|
||||
* from the per-room `room_ban` table the rooms worker owns: that one keeps a player out
|
||||
* of ONE room, this one out of the game.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -199,9 +199,9 @@ export async function getActiveBan(
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a player is banned right now. The hot-path form of `getActiveBan` — `match`
|
||||
* calls it on every matchmake and `auth` on every token grant, and neither has anything
|
||||
* to say about WHICH report did it.
|
||||
* Whether a player is banned right now. The hot-path form of `getActiveBan`, for a caller
|
||||
* that has nothing to say about WHICH report did it. `moderationBlockDetails` is the
|
||||
* caller that does, and reads `getActiveBan` itself.
|
||||
*/
|
||||
export async function isPlayerBanned(
|
||||
db: D1Database,
|
||||
|
||||
+435
-51
@@ -6,6 +6,7 @@ import {
|
||||
getOutfit,
|
||||
getOutfitsByAccounts,
|
||||
inventionDescriptionRejection,
|
||||
inventionLongDescriptionRejection,
|
||||
inventionNameRejection,
|
||||
inventionTagRejection,
|
||||
MAX_BULK_OUTFIT_ACCOUNTS,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
import { authedId, unauthorized } from '../http'
|
||||
import {
|
||||
createInvention,
|
||||
deleteInvention,
|
||||
getFeaturedInventions,
|
||||
getInventionById,
|
||||
getInventionsByIds,
|
||||
@@ -35,6 +37,10 @@ import {
|
||||
getInventionVersion,
|
||||
getMyInventions,
|
||||
getTopInventions,
|
||||
INVENTION_TAG_RESULT,
|
||||
inventionDeleteResult,
|
||||
inventionSaveV9Failure,
|
||||
normalizeInventionTags,
|
||||
ownsAllInventions,
|
||||
parsePermissionLevel,
|
||||
publishInvention,
|
||||
@@ -42,6 +48,7 @@ import {
|
||||
setInventionPrice,
|
||||
setInventionTags,
|
||||
toSaveResult,
|
||||
toSaveResultV9,
|
||||
updateInvention,
|
||||
} from '../inventions-db'
|
||||
import {
|
||||
@@ -54,17 +61,20 @@ import {
|
||||
CustomAvatarItemReportRequest,
|
||||
CustomAvatarItemResponse,
|
||||
CustomAvatarItemsPage,
|
||||
DeleteInventionRequest,
|
||||
ErrorResponse,
|
||||
form,
|
||||
GeneratedGift,
|
||||
GenerateGiftRequest,
|
||||
idParam,
|
||||
intQuery,
|
||||
InventionDeleteResult,
|
||||
InventionDetails,
|
||||
InventionDto,
|
||||
InventionPersonalDetails,
|
||||
InventionReportRequest,
|
||||
InventionSaveResult,
|
||||
InventionSaveV9Result,
|
||||
InventionVersionDto,
|
||||
json,
|
||||
JsonArray,
|
||||
@@ -77,7 +87,9 @@ import {
|
||||
OutfitsMeRequest,
|
||||
OutfitsMeResponse,
|
||||
pageParams,
|
||||
PublishInventionRequest,
|
||||
SaveInventionRequest,
|
||||
SaveInventionV9Request,
|
||||
SetTagsRequest,
|
||||
SetTagsResponse,
|
||||
stringParam,
|
||||
@@ -87,13 +99,14 @@ import {
|
||||
TagFilters,
|
||||
UNAUTHORIZED_RESPONSE,
|
||||
UpdateCustomAvatarItemRequest,
|
||||
UpdateInventionMetadataRequest,
|
||||
UpdatePriceRequest,
|
||||
} from '../openapi'
|
||||
import { createReport } from '../reports-db'
|
||||
|
||||
import type { Context } from 'hono'
|
||||
import type { App } from '../context'
|
||||
import type { SavedInvention } from '../inventions-db'
|
||||
import type { InventionTag, SavedInvention } from '../inventions-db'
|
||||
|
||||
/**
|
||||
* The most ids `POST /api/customAvatarItems/v1/bulk` will resolve. A batch over this answers
|
||||
@@ -133,24 +146,150 @@ async function bulkCustomAvatarItemIds(c: Context<App>): Promise<string[]> {
|
||||
|
||||
/**
|
||||
* The gate every invention write runs through: the caller must be signed in, the
|
||||
* invention must exist, and it must be theirs. Yields the loaded invention, or the
|
||||
* error response to return as-is (401 / 404 / 403).
|
||||
* invention must exist, and it must be theirs. Yields the loaded invention, or why not —
|
||||
* as a reason and the status it maps to, so that a caller answering an envelope can put
|
||||
* the reason where its client will read it instead of in a body that client can't parse.
|
||||
* {@link creatorsInvention} is the rendering the older routes want.
|
||||
*/
|
||||
async function creatorsInventionResult(
|
||||
c: Context<App>,
|
||||
inventionId: number
|
||||
): Promise<
|
||||
{ invention: SavedInvention } | { rejection: string; status: 400 | 401 | 403 | 404 }
|
||||
> {
|
||||
const playerId = await authedId(c)
|
||||
if (playerId === null) return { rejection: 'Unauthorized', status: 401 }
|
||||
if (Number.isNaN(inventionId)) return { rejection: 'inventionId is required', status: 400 }
|
||||
|
||||
const invention = await getInventionById(c.env.DB, inventionId)
|
||||
if (invention === null) return { rejection: 'No such invention', status: 404 }
|
||||
if (invention.CreatorPlayerId !== playerId) {
|
||||
return { rejection: 'Not your invention', status: 403 }
|
||||
}
|
||||
return { invention }
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link creatorsInventionResult} as the older invention writes answer it: the loaded
|
||||
* invention, or the response to return as-is (400 / 401 / 403 / 404).
|
||||
*/
|
||||
async function creatorsInvention(
|
||||
c: Context<App>,
|
||||
inventionId: number
|
||||
): Promise<{ invention: SavedInvention } | { response: Response | Promise<Response> }> {
|
||||
const playerId = await authedId(c)
|
||||
if (playerId === null) return { response: unauthorized(c) }
|
||||
if (Number.isNaN(inventionId)) {
|
||||
return { response: c.json({ error: 'inventionId is required' }, 400) }
|
||||
const gate = await creatorsInventionResult(c, inventionId)
|
||||
if ('invention' in gate) return gate
|
||||
if (gate.status === 401) return { response: unauthorized(c) }
|
||||
if (gate.status === 404) return { response: c.notFound() }
|
||||
return { response: c.json({ error: gate.rejection }, gate.status) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The tags a `{ AutoTags, CustomTags }` request asks for, and whether they were taken —
|
||||
* the block the v9 save sends as `tagsRequest` and `v2/metadata` sends as `TagsRequest`.
|
||||
* Null when the client named no block at all, which each caller reads its own way: a save
|
||||
* stores no tags, an edit leaves the stored ones alone.
|
||||
*
|
||||
* Tags are held to the same rule `v1/settags` applies, but a tag that breaks it costs the
|
||||
* TAGS and not the write: both replies carry a tag result of their own precisely because
|
||||
* the two outcomes are separate, and refusing a save would make the player redo a build
|
||||
* over a hyphen. All the tags go rather than the offending one alone, so nothing is
|
||||
* silently half-applied — the creator re-submits the list and sees what took. Blanks are
|
||||
* skipped rather than counted against it; the client pads its lists with empties.
|
||||
*/
|
||||
function requestedTags(request: unknown): { tags: InventionTag[]; tagResult: number } | null {
|
||||
if (typeof request !== 'object' || request === null) return null
|
||||
|
||||
const lists = request as Record<string, unknown>
|
||||
const strings = (v: unknown): string[] =>
|
||||
Array.isArray(v) ? v.filter((t): t is string => typeof t === 'string') : []
|
||||
const autoTags = strings(lists.AutoTags)
|
||||
const customTags = strings(lists.CustomTags)
|
||||
|
||||
const rejected = [...autoTags, ...customTags].some((raw) => {
|
||||
const tag = raw.trim().toLowerCase()
|
||||
return tag !== '' && inventionTagRejection(tag) !== null
|
||||
})
|
||||
return rejected
|
||||
? { tags: [], tagResult: INVENTION_TAG_RESULT.rejected }
|
||||
: { tags: normalizeInventionTags(autoTags, customTags), tagResult: INVENTION_TAG_RESULT.success }
|
||||
}
|
||||
|
||||
/**
|
||||
* What an invention save produced: the stored record and how its tags fared, or the one
|
||||
* message that refuses it. Both save routes go through {@link createInventionFromBody} to
|
||||
* get one of these and then render it their own way — v6 bare, v9 enveloped — because the
|
||||
* two versions disagree about the shape of a reply, not about what a save is.
|
||||
*/
|
||||
type InventionSaveOutcome =
|
||||
| { rejection: string }
|
||||
| { invention: SavedInvention; tags: InventionTag[]; tagResult: number }
|
||||
|
||||
/**
|
||||
* The invention save both `v6/save` and `v9/save` run through. v9 sends everything v6 does
|
||||
* plus what the invention points at (`referencedUnityAssetIds`), what it says about itself
|
||||
* (`longDescription`, `displayMetadataJson`, `convertedFromInventionId`), `ugcVersion` and
|
||||
* `hasBetaContent`, and the tags that until now needed a second `v1/settags` call. One
|
||||
* reader takes them all: a v6 client sends none of them, and each is optional, so parsing
|
||||
* them here changes nothing about the record a v6 save stores.
|
||||
*/
|
||||
async function createInventionFromBody(
|
||||
c: Context<App>,
|
||||
creatorPlayerId: number,
|
||||
body: Record<string, unknown>
|
||||
): Promise<InventionSaveOutcome> {
|
||||
const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined)
|
||||
const num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined)
|
||||
const bool = (v: unknown): boolean | undefined => (typeof v === 'boolean' ? v : undefined)
|
||||
const list = <T>(v: unknown, is: (x: unknown) => x is T): T[] | undefined =>
|
||||
Array.isArray(v) ? v.filter(is) : undefined
|
||||
const isString = (v: unknown): v is string => typeof v === 'string'
|
||||
const isNumber = (v: unknown): v is number => typeof v === 'number'
|
||||
|
||||
const inventionDataFilename = str(body.inventionDataFilename)?.trim()
|
||||
if (!inventionDataFilename) return { rejection: 'inventionDataFilename is required' }
|
||||
|
||||
// An omitted or blank name/description is defaulted by `createInvention` ("Untitled",
|
||||
// "No description yet"), so only a supplied one is held to the rules — otherwise
|
||||
// saving an unnamed invention would fail the 3-character minimum on a name the
|
||||
// player never typed.
|
||||
const name = str(body.name)?.trim()
|
||||
const nameRejection = name === undefined || name === '' ? null : inventionNameRejection(name)
|
||||
if (nameRejection !== null) return { rejection: nameRejection }
|
||||
|
||||
const description = str(body.description)
|
||||
const descriptionRejection =
|
||||
description === undefined ? null : inventionDescriptionRejection(description)
|
||||
if (descriptionRejection !== null) return { rejection: descriptionRejection }
|
||||
|
||||
// v9 folds `v1/settags` into the save; a client that names no tags gets none.
|
||||
const requested = requestedTags(body.tagsRequest) ?? {
|
||||
tags: [],
|
||||
tagResult: INVENTION_TAG_RESULT.success,
|
||||
}
|
||||
const invention = await getInventionById(c.env.DB, inventionId)
|
||||
if (invention === null) return { response: c.notFound() }
|
||||
if (invention.CreatorPlayerId !== playerId) {
|
||||
return { response: c.json({ error: 'Not your invention' }, 403) }
|
||||
}
|
||||
return { invention }
|
||||
|
||||
const invention = await createInvention(c.env.DB, c.env.CDN_ASSETS, {
|
||||
creatorPlayerId,
|
||||
inventionDataFilename,
|
||||
name,
|
||||
description,
|
||||
imageName: str(body.imageName),
|
||||
instantiationCost: num(body.instantiationCost),
|
||||
lightsCost: num(body.lightsCost),
|
||||
chipsCost: num(body.chipsCost),
|
||||
cloudVariablesCost: num(body.cloudVariablesCost),
|
||||
aiCost: num(body.aiCost),
|
||||
creationRoomId: num(body.creationRoomId),
|
||||
referencedInventions: list(body.referencedInventions, isNumber),
|
||||
ugcVersion: num(body.ugcVersion),
|
||||
hasBetaContent: bool(body.hasBetaContent),
|
||||
referencedUnityAssetIds: list(body.referencedUnityAssetIds, isString),
|
||||
longDescription: str(body.longDescription),
|
||||
displayMetadataJson: str(body.displayMetadataJson),
|
||||
convertedFromInventionId: num(body.convertedFromInventionId),
|
||||
tags: requested.tags,
|
||||
})
|
||||
return { invention, ...requested }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1216,12 +1355,11 @@ export const avatarRoutes = new Hono<App>({ strict: false })
|
||||
const permissionLevel = c.req.query('permissionLevel')
|
||||
const price = Number.parseInt(c.req.query('price') ?? '', 10)
|
||||
|
||||
const published = await publishInvention(
|
||||
c.env.DB,
|
||||
gate.invention.InventionId,
|
||||
permissionLevel === undefined ? undefined : parsePermissionLevel(permissionLevel),
|
||||
Number.isNaN(price) || price < 0 ? undefined : price
|
||||
)
|
||||
const published = await publishInvention(c.env.DB, gate.invention.InventionId, {
|
||||
permissionLevel:
|
||||
permissionLevel === undefined ? undefined : parsePermissionLevel(permissionLevel),
|
||||
price: Number.isNaN(price) || price < 0 ? undefined : price,
|
||||
})
|
||||
return published === null ? c.notFound() : c.json(toSaveResult(published))
|
||||
}
|
||||
)
|
||||
@@ -1670,43 +1808,289 @@ export const avatarRoutes = new Hono<App>({ strict: false })
|
||||
const body = (await c.req.json().catch(() => null)) as Record<string, unknown> | null
|
||||
if (body === null) return c.json({ error: 'Invalid request body' }, 400)
|
||||
|
||||
const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined)
|
||||
const num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined)
|
||||
const outcome = await createInventionFromBody(c, id, body)
|
||||
if ('rejection' in outcome) return c.json({ error: outcome.rejection }, 400)
|
||||
return c.json(toSaveResult(outcome.invention))
|
||||
}
|
||||
)
|
||||
|
||||
const inventionDataFilename = str(body.inventionDataFilename)?.trim()
|
||||
if (!inventionDataFilename) {
|
||||
return c.json({ error: 'inventionDataFilename is required' }, 400)
|
||||
// The same save as the newer client sends it: v6's body plus the invention's
|
||||
// references, its long description and display metadata, what the saved blob is, and
|
||||
// the tags — which v6 clients set afterwards through `v1/settags`. It stores the same
|
||||
// record; what differs is the REPLY, which is enveloped. See `InventionSaveV9Result`:
|
||||
// the client reads `Success` and then `Value.Invention.InventionId`, and a body that
|
||||
// isn't this envelope — a bare `{ error }`, or the empty 401 the other routes answer —
|
||||
// takes it down rather than failing it, which is why every branch below answers one.
|
||||
.post(
|
||||
'/api/inventions/v9/save',
|
||||
describeRoute({
|
||||
tags: ['Inventions'],
|
||||
summary: 'Save a new invention (v9)',
|
||||
description:
|
||||
'`v6/save` plus the fields the newer client sends: `referencedUnityAssetIds`, ' +
|
||||
'`longDescription`, `displayMetadataJson`, `convertedFromInventionId`, ' +
|
||||
'`ugcVersion`, `hasBetaContent`, and a `tagsRequest` carrying the same ' +
|
||||
'`AutoTags`/`CustomTags` lists `v1/settags` takes. Every one is optional and is ' +
|
||||
'stored only when sent, so a body v6 would accept produces the same record here.' +
|
||||
'\n\n' +
|
||||
'The reply is where the two versions part: v9 is ENVELOPED as ' +
|
||||
'`{ Value, Success, Error, error_id }`, with v6’s ' +
|
||||
'`{ Status, Invention, InventionVersion }` inside `Value` alongside a ' +
|
||||
'`TagsResponse`. The client reads `Success` and then ' +
|
||||
'`Value.Invention.InventionId`; `Error` is the only text it ever shows a human.' +
|
||||
'\n\n' +
|
||||
'So a refusal is *also* a 200 carrying `{ Success: false, Error, Value: null }` — ' +
|
||||
'the client dereferences `Value` unguarded when `Success` is true, and treats ' +
|
||||
'anything that isn’t this envelope as a null one. Tags are held to the ' +
|
||||
'`v1/settags` rule (at most 15 letters each), but one that breaks it costs the ' +
|
||||
'tags and not the save: `TagsResponse.Result` comes back non-zero and the creator ' +
|
||||
're-submits them through `v1/settags`.\n\n' +
|
||||
'A freshly saved invention is private: it shows up only in the creator’s own list ' +
|
||||
'until they call `v3/publish`.',
|
||||
security: AUTHED,
|
||||
requestBody: jsonBody(SaveInventionV9Request, 'The invention metadata (camelCase)'),
|
||||
responses: {
|
||||
200: json(
|
||||
InventionSaveV9Result,
|
||||
'The envelope — the stored invention under `Value`, or `Success: false` with ' +
|
||||
'`Error` when the save was refused'
|
||||
),
|
||||
401: json(InventionSaveV9Result, 'The same envelope, refused — not an empty body'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return c.json(inventionSaveV9Failure('Unauthorized'), 401)
|
||||
|
||||
const body = (await c.req.json().catch(() => null)) as Record<string, unknown> | null
|
||||
if (body === null) return c.json(inventionSaveV9Failure('Invalid request body'))
|
||||
|
||||
const outcome = await createInventionFromBody(c, id, body)
|
||||
return c.json(
|
||||
'rejection' in outcome
|
||||
? inventionSaveV9Failure(outcome.rejection)
|
||||
: toSaveResultV9(outcome.invention, outcome.tags, outcome.tagResult)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// Edit an invention's metadata, as the newer client sends it: one PUT with a PascalCase
|
||||
// body where every field but the id is nullable, and NULL means "leave this alone" —
|
||||
// the client sends the whole shape every time and marks the fields it isn't touching.
|
||||
// The tags ride along the way they do on `v9/save`, and the reply is that same
|
||||
// envelope: `v1/update` is the older client's version of this endpoint, query params
|
||||
// and a bare body and all.
|
||||
.put(
|
||||
'/api/inventions/v2/metadata',
|
||||
describeRoute({
|
||||
tags: ['Inventions'],
|
||||
summary: 'Edit an invention’s metadata (v2)',
|
||||
description:
|
||||
'Creator only. Every field but `InventionId` is nullable and a null one is left ' +
|
||||
'as it is — the client sends the whole shape on every edit — so this is a patch, ' +
|
||||
'not a replace. An empty string is not a null: it is how a creator CLEARS a ' +
|
||||
'description, long description or image. `Name` is the exception, since a nameless ' +
|
||||
'invention isn’t a thing the client can draw: it is held to the same 3–24 ' +
|
||||
'character rule `v6/save` enforces, which an empty name fails.\n\n' +
|
||||
'`TagsRequest` replaces both tag lists wholesale, exactly as `v1/settags` does; a ' +
|
||||
'null one leaves the stored tags alone. A tag that breaks the tag rule costs the ' +
|
||||
'tags and not the edit — `TagsResponse.Result` comes back non-zero.\n\n' +
|
||||
'Answers the enveloped result `v9/save` answers, carrying the UPDATED invention: ' +
|
||||
'the client re-renders the detail page from `Value.Invention`. Refusals — an ' +
|
||||
'unknown invention and someone else’s alike — are `Success: false` with a null ' +
|
||||
'`Value` rather than a bare error body, which that client cannot parse.',
|
||||
security: AUTHED,
|
||||
requestBody: jsonBody(UpdateInventionMetadataRequest, 'The fields to change'),
|
||||
responses: {
|
||||
200: json(
|
||||
InventionSaveV9Result,
|
||||
'The envelope — the updated invention under `Value`, or `Success: false` with ' +
|
||||
'`Error` when the edit was refused'
|
||||
),
|
||||
401: json(InventionSaveV9Result, 'The same envelope, refused — not an empty body'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const body = (await c.req.json().catch(() => null)) as Record<string, unknown> | null
|
||||
if (body === null) return c.json(inventionSaveV9Failure('Invalid request body'))
|
||||
|
||||
// The id rides in the body here, not the query string.
|
||||
const gate = await creatorsInventionResult(
|
||||
c,
|
||||
typeof body.InventionId === 'number' ? body.InventionId : Number.NaN
|
||||
)
|
||||
// Only a missing token is answered as a transport failure. An unknown invention
|
||||
// or someone else's is a domain answer the client is meant to read — its own
|
||||
// status enum has DoesNotExist and NotCreator members — so it goes in the
|
||||
// envelope, where the message reaches a human.
|
||||
if ('rejection' in gate) {
|
||||
return gate.status === 401
|
||||
? c.json(inventionSaveV9Failure(gate.rejection), 401)
|
||||
: c.json(inventionSaveV9Failure(gate.rejection))
|
||||
}
|
||||
|
||||
// Null is "leave it"; a string, empty or not, is an edit.
|
||||
const edited = (key: string): string | undefined =>
|
||||
typeof body[key] === 'string' ? body[key] : undefined
|
||||
const name = edited('Name')?.trim()
|
||||
const description = edited('Description')
|
||||
const longDescription = edited('LongDescription')
|
||||
|
||||
// An omitted or blank name/description is defaulted by `createInvention` ("Untitled",
|
||||
// "No description yet"), so only a supplied one is held to the rules — otherwise
|
||||
// saving an unnamed invention would fail the 3-character minimum on a name the
|
||||
// player never typed.
|
||||
const name = str(body.name)?.trim()
|
||||
const nameRejection = name === undefined || name === '' ? null : inventionNameRejection(name)
|
||||
if (nameRejection !== null) return c.json({ error: nameRejection }, 400)
|
||||
|
||||
const description = str(body.description)
|
||||
const descriptionRejection =
|
||||
description === undefined ? null : inventionDescriptionRejection(description)
|
||||
if (descriptionRejection !== null) return c.json({ error: descriptionRejection }, 400)
|
||||
|
||||
const invention = await createInvention(c.env.DB, c.env.CDN_ASSETS, {
|
||||
creatorPlayerId: id,
|
||||
inventionDataFilename,
|
||||
for (const rejection of [
|
||||
name === undefined ? null : inventionNameRejection(name),
|
||||
description === undefined ? null : inventionDescriptionRejection(description),
|
||||
longDescription === undefined
|
||||
? null
|
||||
: inventionLongDescriptionRejection(longDescription),
|
||||
]) {
|
||||
if (rejection !== null) return c.json(inventionSaveV9Failure(rejection))
|
||||
}
|
||||
|
||||
// A null TagsRequest leaves the stored tags alone, and the reply still reports
|
||||
// them: the client reads the list back as the tags the invention now has, not as
|
||||
// the ones this call changed.
|
||||
const requested = requestedTags(body.TagsRequest)
|
||||
const updated = await updateInvention(c.env.DB, gate.invention.InventionId, {
|
||||
name,
|
||||
description,
|
||||
imageName: str(body.imageName),
|
||||
instantiationCost: num(body.instantiationCost),
|
||||
lightsCost: num(body.lightsCost),
|
||||
chipsCost: num(body.chipsCost),
|
||||
cloudVariablesCost: num(body.cloudVariablesCost),
|
||||
aiCost: num(body.aiCost),
|
||||
creationRoomId: num(body.creationRoomId),
|
||||
referencedInventions: Array.isArray(body.referencedInventions)
|
||||
? body.referencedInventions.filter((v): v is number => typeof v === 'number')
|
||||
: undefined,
|
||||
longDescription,
|
||||
imageName: edited('ImageName'),
|
||||
tags: requested?.tags,
|
||||
})
|
||||
return c.json(toSaveResult(invention))
|
||||
if (updated === null) return c.json(inventionSaveV9Failure('No such invention'))
|
||||
return c.json(
|
||||
toSaveResultV9(
|
||||
updated,
|
||||
updated.Tags ?? [],
|
||||
requested?.tagResult ?? INVENTION_TAG_RESULT.success
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// Publish an invention, as the newer client sends it: a PascalCase body instead of a
|
||||
// query string, and an Accessibility of its own — where `v3/publish` only ever flipped
|
||||
// the published flag, this decides whether the result can be FOUND. Same enveloped
|
||||
// reply as `v9/save`, carrying the published invention.
|
||||
.post(
|
||||
'/api/inventions/v4/publish',
|
||||
describeRoute({
|
||||
tags: ['Inventions'],
|
||||
summary: 'Publish an invention (v4)',
|
||||
description:
|
||||
'What puts an invention into search and the feeds. Creator only.\n\n' +
|
||||
'`Permission` is the `GeneralPermission` other players get, as a raw ladder ' +
|
||||
'number (the publish sheet sends 20, UseOnly). `Accessibility` says where it can ' +
|
||||
'be found — 1 (Public) lists it, 2 (Unlisted) publishes it reachable by id but ' +
|
||||
'keeps it out of browse and search. A null `Price` leaves the price alone rather ' +
|
||||
'than zeroing it, so re-publishing something that was for sale doesn’t give it ' +
|
||||
'away; every field but `InventionId` is nullable and an omitted one keeps what ' +
|
||||
'the invention has.\n\n' +
|
||||
'Publishing is not undone here, and re-publishing doesn’t re-date the first ' +
|
||||
'publish. Refusals answer `Success: false` with a null `Value`, the way ' +
|
||||
'`v9/save` does.',
|
||||
security: AUTHED,
|
||||
requestBody: jsonBody(PublishInventionRequest, 'What the publish decides'),
|
||||
responses: {
|
||||
200: json(
|
||||
InventionSaveV9Result,
|
||||
'The envelope — the published invention under `Value`, or `Success: false` ' +
|
||||
'with `Error` when the publish was refused'
|
||||
),
|
||||
401: json(InventionSaveV9Result, 'The same envelope, refused — not an empty body'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const body = (await c.req.json().catch(() => null)) as Record<string, unknown> | null
|
||||
if (body === null) return c.json(inventionSaveV9Failure('Invalid request body'))
|
||||
|
||||
const gate = await creatorsInventionResult(
|
||||
c,
|
||||
typeof body.InventionId === 'number' ? body.InventionId : Number.NaN
|
||||
)
|
||||
// As on `v2/metadata`: only a missing token is a transport failure. The rest are
|
||||
// answers the client is meant to read out of the envelope.
|
||||
if ('rejection' in gate) {
|
||||
return gate.status === 401
|
||||
? c.json(inventionSaveV9Failure(gate.rejection), 401)
|
||||
: c.json(inventionSaveV9Failure(gate.rejection))
|
||||
}
|
||||
|
||||
// Null is "leave it". The permission and accessibility are taken as sent rather
|
||||
// than checked against the ladder, the way `parsePermissionLevel` already accepts
|
||||
// a raw number: the ladders are the client's, and a level this server hasn't heard
|
||||
// of is better stored than swapped for one the creator didn't pick.
|
||||
const int = (key: string): number | undefined =>
|
||||
typeof body[key] === 'number' && Number.isInteger(body[key]) ? body[key] : undefined
|
||||
const price = int('Price')
|
||||
|
||||
const published = await publishInvention(c.env.DB, gate.invention.InventionId, {
|
||||
permissionLevel: int('Permission'),
|
||||
accessibility: int('Accessibility'),
|
||||
// A negative price is dropped rather than stored, as it is on `v3/publish`.
|
||||
price: price !== undefined && price < 0 ? undefined : price,
|
||||
})
|
||||
if (published === null) return c.json(inventionSaveV9Failure('No such invention'))
|
||||
return c.json(toSaveResultV9(published, published.Tags ?? []))
|
||||
}
|
||||
)
|
||||
|
||||
// Delete an invention. The newer client's shape: a POST with a PascalCase body
|
||||
// carrying nothing but the id. Auth-gated, creator only — the only thing that may
|
||||
// remove an invention is the account that made it, not a co-owner and not a buyer.
|
||||
//
|
||||
// The record and everything inside it (versions, tags, referenced-invention lists)
|
||||
// go in one DELETE; the data blob in R2 and the `inventory_invention` rows of
|
||||
// players who bought it are left alone. See `deleteInvention` for why.
|
||||
.post(
|
||||
'/api/inventions/v2/delete',
|
||||
describeRoute({
|
||||
tags: ['Inventions'],
|
||||
summary: 'Delete an invention',
|
||||
description:
|
||||
'Creator only — a buyer or a co-owner cannot delete someone else’s invention. ' +
|
||||
'The record goes entirely: its versions, tags and referenced-invention lists live ' +
|
||||
'in the same row.\n\n' +
|
||||
'What survives is deliberate. The data blob stays in storage, because nothing ' +
|
||||
'here knows whether another record still points at that filename. The ownership ' +
|
||||
'rows of players who bought it stay too — a delete must not rewrite what someone ' +
|
||||
'else paid for — and they fall out of every list on their own, since an owned id ' +
|
||||
'with no invention row behind it is skipped.\n\n' +
|
||||
'Answers the `{ Value, Success, Error, error_id }` envelope the other v2+ ' +
|
||||
'invention routes use, with `Value` NULL: the invention is gone, so there is ' +
|
||||
'nothing to redraw from and the client reads only `Success`. Refusals — an ' +
|
||||
'unknown invention and someone else’s alike — are `Success: false` with a ' +
|
||||
'message, not a bare error body that client cannot parse.',
|
||||
security: AUTHED,
|
||||
requestBody: jsonBody(DeleteInventionRequest, 'The invention to delete'),
|
||||
responses: {
|
||||
200: json(InventionDeleteResult, 'The delete envelope, `Value` null either way'),
|
||||
401: json(InventionDeleteResult, 'The same envelope, refused — not an empty body'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const body = (await c.req.json().catch(() => null)) as Record<string, unknown> | null
|
||||
if (body === null) return c.json(inventionDeleteResult('Invalid request body'))
|
||||
|
||||
// The id rides in the body, as it does on `v2/metadata` and `v4/publish`.
|
||||
const gate = await creatorsInventionResult(
|
||||
c,
|
||||
typeof body.InventionId === 'number' ? body.InventionId : Number.NaN
|
||||
)
|
||||
// As on those two: only a missing token is a transport failure. An unknown
|
||||
// invention or someone else's is a domain answer the client reads out of the
|
||||
// envelope, where the message reaches a human.
|
||||
if ('rejection' in gate) {
|
||||
return gate.status === 401
|
||||
? c.json(inventionDeleteResult(gate.rejection), 401)
|
||||
: c.json(inventionDeleteResult(gate.rejection))
|
||||
}
|
||||
|
||||
// The gate already loaded the row, so a null here is a race — someone deleted it
|
||||
// between the two reads — and lands where the client would put it anyway: gone.
|
||||
const deleted = await deleteInvention(c.env.DB, gate.invention.InventionId)
|
||||
return c.json(
|
||||
deleted === null ? inventionDeleteResult('No such invention') : inventionDeleteResult()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -29,17 +29,19 @@ export const configRoutes = new Hono<App>({ strict: false })
|
||||
tags: ['Config'],
|
||||
summary: 'Analytics keys',
|
||||
description:
|
||||
'The Amplitude / StatSig / RudderStack keys the client initialises its analytics ' +
|
||||
'with. This server collects nothing, so the keys are placeholders and RudderStack ' +
|
||||
'is off — but the client needs the object to finish loading.',
|
||||
'The Amplitude / RudderStack / StatSig keys the client initialises its analytics ' +
|
||||
'with. This server collects nothing, so the keys are blank and RudderStack and ' +
|
||||
'StatSig are off — but the client needs the object to finish loading.',
|
||||
responses: { 200: json(AmplitudeConfig, 'Placeholder analytics keys') },
|
||||
}),
|
||||
(c) =>
|
||||
c.json({
|
||||
AmplitudeKey: 'a',
|
||||
StatSigKey: 'a',
|
||||
RudderStackKey: 'a',
|
||||
AmplitudeKey: '',
|
||||
UseRudderStack: false,
|
||||
RudderStackKey: '',
|
||||
UseStatSig: false,
|
||||
StatSigKey: '',
|
||||
StatSigEnvironment: 0,
|
||||
})
|
||||
)
|
||||
.get(
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import { Hono } from 'hono'
|
||||
import { describeRoute } from 'hono-openapi'
|
||||
|
||||
import {
|
||||
canModerateRoom,
|
||||
deletePresence,
|
||||
getPlayerIdsInInstance,
|
||||
getPresences,
|
||||
getRoomById,
|
||||
getStoredRoomInstance,
|
||||
MessageType,
|
||||
refreshInstanceFullness,
|
||||
} from '@repo/domain'
|
||||
import { logger } from '@repo/hono-helpers'
|
||||
|
||||
// The notification-type ids the hub carries (owned by the `notify` worker). Imported as a
|
||||
// value — the enum has no runtime dependencies.
|
||||
import { KickReportCategory } from '../../../notify/src/notification-payloads'
|
||||
import { NotificationType } from '../../../notify/src/notification-types'
|
||||
import { authedId, authedRoles, unauthorized } from '../http'
|
||||
import {
|
||||
AUTHED,
|
||||
@@ -9,18 +25,23 @@ import {
|
||||
CreateWarningRequest,
|
||||
DeviceIdRequest,
|
||||
form,
|
||||
InstantKickRequest,
|
||||
json,
|
||||
JsonArray,
|
||||
jsonBody,
|
||||
ModerationBlockDetails,
|
||||
SuccessErrorEnvelope,
|
||||
UNAUTHORIZED_RESPONSE,
|
||||
VoteToKickReason,
|
||||
VoteToKickRequest,
|
||||
} from '../openapi'
|
||||
import { createReport } from '../reports-db'
|
||||
import { createReport, getActiveBan } from '../reports-db'
|
||||
import { createWarning } from '../warnings-db'
|
||||
|
||||
import type { Context } from 'hono'
|
||||
import type { ModerationKickPayload } from '../../../notify/src/notification-payloads'
|
||||
import type { App } from '../context'
|
||||
import type { ReportRow } from '../reports-db'
|
||||
|
||||
/**
|
||||
* Roles allowed to hand down a warning — the operator-granted elevated roles the auth
|
||||
@@ -82,16 +103,209 @@ const VOTE_TO_KICK_REASONS = [
|
||||
{ Reason: 'Not following game rules', ReportCategory: 6 },
|
||||
] as const
|
||||
|
||||
/** The notifications hub is a single global DO instance (see the `notify` worker). */
|
||||
const HUB_INSTANCE = 'global'
|
||||
|
||||
/**
|
||||
* Eject players from the instance they're standing in — the `ModerationKick` frame (id 22)
|
||||
* the client acts on to leave a room, the same one a room ban sends (`rooms`:
|
||||
* `pushRoomBan`). This one only kicks: `IsBan` is false, so nothing keeps them from walking
|
||||
* straight back in, and no ban row exists to lift.
|
||||
*
|
||||
* Sent EPHEMERALLY, unlike the ban's frame, and to the whole batch in one round-trip. A
|
||||
* kick is only true of the moment it happened: queued and delivered on the player's next
|
||||
* connect it would throw them out of some unrelated session hours later. A recipient who
|
||||
* has already gone offline needs no kick anyway.
|
||||
*
|
||||
* `GameSessionId` is the instance they're being removed from — every recipient is in it,
|
||||
* which is what the caller checked before this runs. `IsHostKick` is always true: this
|
||||
* endpoint is the room's own staff acting, never the room majority vote-kicking (that path
|
||||
* would carry `VoteKick` and false). Built against the client's recovered payload interface
|
||||
* so a renamed key fails the build rather than vanishing on the wire.
|
||||
*
|
||||
* Best-effort — presence is already deleted by the time this runs, so a hub hiccup must
|
||||
* not fail the request.
|
||||
*/
|
||||
async function pushInstantKick(
|
||||
c: Context<App>,
|
||||
playerIds: number[],
|
||||
gameSessionId: number,
|
||||
roomName: string,
|
||||
moderatorId: number
|
||||
): Promise<void> {
|
||||
const frame: ModerationKickPayload = {
|
||||
ReportCategory: KickReportCategory.Moderator,
|
||||
Duration: 0,
|
||||
GameSessionId: gameSessionId,
|
||||
IsHostKick: true,
|
||||
Message: `You have been kicked from ${roomName}.`,
|
||||
PlayerIdReporter: moderatorId,
|
||||
IsBan: false,
|
||||
IsVoiceModAutoban: false,
|
||||
IsWarning: false,
|
||||
VoteKickReason: '',
|
||||
TimeoutStartedAt: null,
|
||||
}
|
||||
try {
|
||||
await c.env.RECFLARE_NOTIFICATIONS_HUB.getByName(HUB_INSTANCE).notifyPlayersEphemeral(
|
||||
playerIds,
|
||||
NotificationType.ModerationKick,
|
||||
{ ...frame }
|
||||
)
|
||||
} catch (err) {
|
||||
logger.error('failed to push ModerationKick notification', {
|
||||
playerIds,
|
||||
gameSessionId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What a vote-to-kick Message's `Data` says, BEFORE it is serialized. It reaches the
|
||||
* client as an escaped JSON string, never as a nested object — a Message's `Data` is a
|
||||
* string on the wire like every other Message's, and the client's decoder rejects an
|
||||
* object outright: `expected:'String Begin Token', actual:'{'`, which aborts the whole
|
||||
* notification rather than dropping the field. Serialize it with {@link voteToKickData}.
|
||||
*
|
||||
* `PlayerId` is the account id as a STRING — the reference passes the posted form field
|
||||
* straight through, and this mirrors it verbatim.
|
||||
*
|
||||
* `Response` is the empty string even though the caller posted their own vote: the frame
|
||||
* is the PROMPT put to everyone else, so it carries no answer yet. The caller's `Response`
|
||||
* is theirs alone and is not relayed.
|
||||
*/
|
||||
interface VoteToKickData {
|
||||
PlayerId: string
|
||||
Response: string
|
||||
GameSessionId: number
|
||||
}
|
||||
|
||||
/** Serialize a {@link VoteToKickData} into the escaped JSON string `Data` carries. */
|
||||
const voteToKickData = (data: VoteToKickData): string => JSON.stringify(data)
|
||||
|
||||
/**
|
||||
* The Message a vote-to-kick frame carries — the same four fields as every other Message
|
||||
* this server sends (see the `social` routes' `Message`), `Data` string included. A type
|
||||
* alias rather than an interface: the hub's send takes an index-signature record, which
|
||||
* only aliases satisfy implicitly.
|
||||
*/
|
||||
type VoteToKickMessage = {
|
||||
FromPlayerId: number
|
||||
ToPlayerId: number
|
||||
Type: number
|
||||
Data: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a vote-to-kick to one player — a `MessageReceived` frame carrying a Message of type
|
||||
* 5 (`VoteToKick`), the frame their client raises the vote prompt from. Resolves false when
|
||||
* the hub could not be reached, which the caller reports honestly: nothing stores a vote,
|
||||
* so the notification is the whole delivery.
|
||||
*
|
||||
* EPHEMERAL, unlike the messages the social routes send. A vote belongs to the moment it
|
||||
* was called: queued for an offline player, it would raise a prompt on their next connect
|
||||
* about a session that ended hours ago, and there would be nothing left to vote on.
|
||||
*/
|
||||
async function pushVoteToKick(c: Context<App>, message: VoteToKickMessage): Promise<boolean> {
|
||||
try {
|
||||
await c.env.RECFLARE_NOTIFICATIONS_HUB.getByName(HUB_INSTANCE).notifyPlayerEphemeral(
|
||||
message.ToPlayerId,
|
||||
NotificationType.MessageReceived,
|
||||
message
|
||||
)
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error('failed to push VoteToKick MessageReceived notification', {
|
||||
toPlayerId: message.ToPlayerId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `Duration` on a permanent ban. The client's field is a 32-bit int of seconds that PAIRS
|
||||
* with `TimeoutStartedAt` — start + duration is the end of the block — so a ban with no
|
||||
* end gets the largest value the field holds, 68 years past its start.
|
||||
*/
|
||||
const PERMANENT_BAN_DURATION = 2_147_483_647
|
||||
|
||||
/**
|
||||
* The "not blocked" answer — the reference server's stub `ReturnModerationBlockDetails()`,
|
||||
* widened to every key the client's `ModerationBlockDetail` decoder names (16 on the wire;
|
||||
* the 2025 build's formatter reads them all). The ones past the stub's nine are the block
|
||||
* kinds and screen dressings this server never uses — a device ban, a warning, the
|
||||
* vote-kick reason, an associated account, the creator code of conduct, the top/bottom
|
||||
* message overrides — so they carry their "none" values on every answer.
|
||||
*/
|
||||
const NOT_BLOCKED = {
|
||||
ReportCategory: -1,
|
||||
Duration: 0,
|
||||
GameSessionId: 0,
|
||||
IsHostKick: false,
|
||||
Message: null,
|
||||
PlayerIdReporter: null,
|
||||
IsBan: false,
|
||||
IsVoiceModAutoban: false,
|
||||
IsDeviceBan: false,
|
||||
IsWarning: false,
|
||||
VoteKickReason: null,
|
||||
TimeoutStartedAt: null,
|
||||
AssociatedAccountUsername: null,
|
||||
ShowCreatorCodeOfConduct: false,
|
||||
TopMessageOverride: null,
|
||||
BottomMessageOverride: null,
|
||||
}
|
||||
|
||||
/**
|
||||
* The block details for a ban in force — the `report` row a moderator set `banned` on.
|
||||
*
|
||||
* `Duration` and `TimeoutStartedAt` are a PAIR in the client: the block runs from the
|
||||
* start for the duration. The start is the report's `created_at` — nothing records when
|
||||
* the ban itself was handed down, and the report is the record the ban rests on — and the
|
||||
* duration is the seconds from there to `ban_expires`, so the two sum to the expiry; or
|
||||
* `PERMANENT_BAN_DURATION` when there is none. The category is the one the report was
|
||||
* filed under, so the client's ban screen names the reason. `Message` is a fixed "Rule
|
||||
* violation" rather than the report's `details` — those are the REPORTER's words, and the
|
||||
* banned player isn't shown them, for the same reason `PlayerIdReporter` stays null: the
|
||||
* reporter is not a host who kicked them, and naming them would tell the banned player who
|
||||
* reported them. Everything else keeps its `NOT_BLOCKED` value: the other block kinds and
|
||||
* screen dressings, none of which this server hands out.
|
||||
*/
|
||||
function banBlockDetails(ban: ReportRow) {
|
||||
const startedAt = Date.parse(ban.created_at)
|
||||
const duration =
|
||||
ban.ban_expires === null
|
||||
? PERMANENT_BAN_DURATION
|
||||
: Math.max(1, Math.ceil((Date.parse(ban.ban_expires) - startedAt) / 1000))
|
||||
return {
|
||||
...NOT_BLOCKED,
|
||||
ReportCategory: ban.report_category,
|
||||
Duration: duration,
|
||||
IsBan: true,
|
||||
Message: 'Rule violation',
|
||||
TimeoutStartedAt: ban.created_at,
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Player reporting ------------------------------------------------------
|
||||
export const moderationRoutes = new Hono<App>({ strict: false })
|
||||
// Whether the caller is currently blocked (banned / timed out / host-kicked). Bans
|
||||
// are stored (a report row with `banned` set) and enforced at matchmake and at login,
|
||||
// but this endpoint is not wired to them, so it's always the "not blocked" answer —
|
||||
// the reference server's stub `ReturnModerationBlockDetails()`.
|
||||
// `ReportCategory` is `Unknown` (-1) rather than 0, which is a real category;
|
||||
// `Message` is null, not the empty string that stub sends — the client distinguishes
|
||||
// "no message" from a blank one. `IsVoiceModAutoban`/`TimeoutStartedAt` are on the
|
||||
// DTO but left unset there, so they go out with their C# defaults.
|
||||
// Whether the caller is currently blocked (banned / timed out / host-kicked). The one
|
||||
// kind of block this server has is the account-wide ban — a `report` row with `banned`
|
||||
// set (see `getActiveBan`), the same row matchmake refuses on — so a caller with one in
|
||||
// force gets it described here, and everyone else gets the "not blocked" answer of the
|
||||
// reference server's stub `ReturnModerationBlockDetails()`. This is the screen a banned
|
||||
// player is shown, which is why `auth` still issues them a token: without one the client
|
||||
// never gets here, and the ban reads as a failed sign-in.
|
||||
// Only the caller's OWN account is consulted, not the evasion arms `resolveBan` adds
|
||||
// at matchmake and login: this screen explains a ban handed to this account, and a
|
||||
// player blocked for sharing a network with a banned one has no report row to show.
|
||||
// In the "not blocked" answer `ReportCategory` is `Unknown` (-1) rather than 0, which
|
||||
// is a real category, and `Message` is null, not the empty string that stub sends —
|
||||
// the client distinguishes "no message" from a blank one. `IsVoiceModAutoban` /
|
||||
// `TimeoutStartedAt` are on the DTO but unset there, so they go out with their C#
|
||||
// defaults.
|
||||
// The newer client POSTs this with no body despite it being a pure read; it answers
|
||||
// GET too, so the path is reachable from either build.
|
||||
.on(
|
||||
@@ -101,29 +315,40 @@ export const moderationRoutes = new Hono<App>({ strict: false })
|
||||
tags: ['Moderation'],
|
||||
summary: 'Whether the caller is blocked',
|
||||
description:
|
||||
'Ban / timeout / host-kick state for the caller. Bans are stored (a `report` row ' +
|
||||
'with `banned` set) and enforced at matchmake and at login, but this endpoint is ' +
|
||||
'not wired to them, so it is always the “not blocked” answer, following the ' +
|
||||
'reference server’s stub: `ReportCategory` is `Unknown` (-1) rather than 0, which ' +
|
||||
'is a real category, and `Message` is null rather than the empty string that stub ' +
|
||||
'sends — the client distinguishes “no message” from a blank one. ' +
|
||||
'`IsVoiceModAutoban` and `TimeoutStartedAt` are on the DTO but unset by that ' +
|
||||
'stub, so they carry their defaults. Answers GET or POST: the newer client POSTs ' +
|
||||
'it with no body.',
|
||||
responses: { 200: json(ModerationBlockDetails, 'Always “not blocked”') },
|
||||
'Ban / timeout / host-kick state for the caller. The one block this server hands ' +
|
||||
'out is the account-wide ban — a `report` row with `banned` set, the same row ' +
|
||||
'matchmake refuses on (login still issues a token, so the client can reach this ' +
|
||||
'screen) — so a caller with one in force gets ' +
|
||||
'`IsBan: true`, the `ReportCategory` the report was filed under, the fixed ' +
|
||||
'`Message` “Rule violation”, and the block’s span as the pair the client reads ' +
|
||||
'them as: `TimeoutStartedAt` is the report’s `created_at` and `Duration` the ' +
|
||||
'seconds from there to `ban_expires` (2147483647, the int32 max, for a permanent ' +
|
||||
'ban). ' +
|
||||
'`PlayerIdReporter` stays null: it names a kicking host, and the reporter is not ' +
|
||||
'shown to the player they reported. Only the caller’s own account is consulted, ' +
|
||||
'not the ban-evasion arms.\n\n' +
|
||||
'Everyone else gets the reference server’s stub “not blocked” answer: ' +
|
||||
'`ReportCategory` is `Unknown` (-1) rather than 0, which is a real category, and ' +
|
||||
'`Message` is null rather than the empty string that stub sends — the client ' +
|
||||
'distinguishes “no message” from a blank one. `IsVoiceModAutoban` and ' +
|
||||
'`TimeoutStartedAt` are on the DTO but unset by that stub, so they carry their ' +
|
||||
'defaults, as do the seven keys past the stub’s nine that the 2025 client’s decoder ' +
|
||||
'names (`IsDeviceBan`, `IsWarning`, `VoteKickReason`, `AssociatedAccountUsername`, ' +
|
||||
'`ShowCreatorCodeOfConduct`, `TopMessageOverride`, `BottomMessageOverride`) — ' +
|
||||
'block kinds and screen dressings this server never uses. Answers GET or POST: ' +
|
||||
'the newer client POSTs it with no body.',
|
||||
security: AUTHED,
|
||||
responses: {
|
||||
200: json(ModerationBlockDetails, 'The caller’s block, or “not blocked”'),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
},
|
||||
}),
|
||||
(c) =>
|
||||
c.json({
|
||||
ReportCategory: -1,
|
||||
Duration: 0,
|
||||
GameSessionId: 0,
|
||||
IsBan: false,
|
||||
IsHostKick: false,
|
||||
IsVoiceModAutoban: false,
|
||||
Message: null,
|
||||
PlayerIdReporter: null,
|
||||
TimeoutStartedAt: null,
|
||||
})
|
||||
async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
const ban = await getActiveBan(c.env.DB, id)
|
||||
return c.json(ban ? banBlockDetails(ban) : NOT_BLOCKED)
|
||||
}
|
||||
)
|
||||
// The reasons the client offers when a player starts a vote-to-kick. Order matters —
|
||||
// the client renders them in the order they arrive — and the list is grouped by the
|
||||
@@ -209,10 +434,10 @@ export const moderationRoutes = new Hono<App>({ strict: false })
|
||||
tags: ['Moderation'],
|
||||
summary: 'Submit a player report',
|
||||
description:
|
||||
'Records a player report in the `report` table; nothing dedupes the rows, and ' +
|
||||
'`moderationBlockDetails` still answers “not blocked” unconditionally. A report ' +
|
||||
'Records a player report in the `report` table; nothing dedupes the rows. A report ' +
|
||||
'is filed unbanned — a moderator converts one into an account-wide ban by setting ' +
|
||||
'`banned` on the row, which is what matchmaking and `/connect/token` refuse on.\n\n' +
|
||||
'`banned` on the row, which is what matchmaking refuses on and what ' +
|
||||
'`moderationBlockDetails` describes to the banned player.\n\n' +
|
||||
'The reporter is the caller (from the bearer token), NOT a body field. Only ' +
|
||||
'`PlayerIdReported` is required; the client omits whatever it has no value for ' +
|
||||
'(a report raised outside a room carries no `RoomId`), and those are stored as ' +
|
||||
@@ -257,6 +482,212 @@ export const moderationRoutes = new Hono<App>({ strict: false })
|
||||
}
|
||||
)
|
||||
|
||||
// A player calling a vote to kick another. Ungated by role — anyone may start one —
|
||||
// but both players have to be standing in the session the vote is called in, which is
|
||||
// what stops a client putting a vote to a room it isn't in, about someone who isn't
|
||||
// there. Nothing tallies the votes yet: this relays the prompt and no more.
|
||||
.post(
|
||||
'/api/PlayerReporting/v3/voteToKick',
|
||||
describeRoute({
|
||||
tags: ['Moderation'],
|
||||
summary: 'Call a vote to kick a player',
|
||||
description:
|
||||
'Puts a vote-to-kick to the room instance. Open to any player — no role is ' +
|
||||
'required — but BOTH the caller and `PlayerId` must have a live `presence` row in ' +
|
||||
'the instance `GameSessionId` names, or the call is refused with a 403. That is ' +
|
||||
'the whole gate: without it a client could raise a vote in a session it is not ' +
|
||||
'in, or against a player who is not there.\n\n' +
|
||||
'Everyone else in that instance — the player being voted on included, since a ' +
|
||||
'vote is called in front of them — gets a `MessageReceived` frame carrying a ' +
|
||||
'Message of type 5 (`VoteToKick`). The caller is left out: they have voted ' +
|
||||
'already, and their own `Response` is what they posted.\n\n' +
|
||||
'`Data` is an ESCAPED JSON STRING — `"{\\"PlayerId\\":\\"205\\",…}"`, not a nested ' +
|
||||
'object. A Message’s `Data` is a string on the wire, and an object there fails the ' +
|
||||
"client’s decoder outright (`expected:'String Begin Token', actual:'{'`), " +
|
||||
'aborting the notification rather than dropping the field. Inside it, `PlayerId` ' +
|
||||
'is the account id as a STRING, as the reference relays it, and `Response` is ' +
|
||||
'empty — the frame is the question, not an answer.\n\n' +
|
||||
'The frames are EPHEMERAL: a vote belongs to the moment it was called, so an ' +
|
||||
'offline player gets nothing rather than a prompt about a dead session on their ' +
|
||||
'next connect.\n\n' +
|
||||
'Nothing is stored — no tally, no report row, and `Reason` is accepted and ' +
|
||||
'unused. Answers the same lowercase `{ success, error }` envelope as the report ' +
|
||||
'write; a hub failure for any recipient is reported honestly as a 500, since ' +
|
||||
'with nothing behind it the frame is the whole delivery.',
|
||||
security: AUTHED,
|
||||
requestBody: form(VoteToKickRequest, 'The vote'),
|
||||
responses: {
|
||||
200: json(SuccessErrorEnvelope, '`{ success: true, error: "" }`'),
|
||||
400: json(SuccessErrorEnvelope, 'No `PlayerId` or no `GameSessionId`'),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
403: json(SuccessErrorEnvelope, 'Either player is not in that game session'),
|
||||
500: json(SuccessErrorEnvelope, 'The notifications hub could not be reached'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const voterId = await authedId(c)
|
||||
if (voterId === null) return unauthorized(c)
|
||||
|
||||
const body = await c.req.parseBody().catch(() => ({}) as Record<string, unknown>)
|
||||
// Kept as posted for the frame — `Data.PlayerId` goes out as the string the
|
||||
// reference relays — but parsed here to check it against presence.
|
||||
const playerIdField = formField(body, c, 'PlayerId')
|
||||
const playerId = asInt(playerIdField)
|
||||
if (playerIdField === undefined || playerId === null) {
|
||||
return c.json({ success: false, error: 'PlayerId is required' }, 400)
|
||||
}
|
||||
const gameSessionId = asInt(formField(body, c, 'GameSessionId'))
|
||||
if (gameSessionId === null) {
|
||||
return c.json({ success: false, error: 'GameSessionId is required' }, 400)
|
||||
}
|
||||
|
||||
// One read for both players. A vote may only be called by someone standing in the
|
||||
// session, about someone standing in the same one — the session is read from live
|
||||
// presence, never from the body, so neither side can be asserted by the client.
|
||||
const presences = await getPresences<{ roomInstanceId?: number }>(c.env.DB, [
|
||||
voterId,
|
||||
playerId,
|
||||
])
|
||||
const isHere = (id: number) =>
|
||||
presences.get(id)?.roomInstance?.roomInstanceId === gameSessionId
|
||||
if (!isHere(voterId)) {
|
||||
return c.json({ success: false, error: 'You are not in that game session!' }, 403)
|
||||
}
|
||||
if (!isHere(playerId)) {
|
||||
return c.json({ success: false, error: 'That player is not in that game session!' }, 403)
|
||||
}
|
||||
|
||||
// The room votes, so the audience is everyone standing there — the player being
|
||||
// voted on included; a vote is called in front of them. The caller is dropped:
|
||||
// their vote is the one they just posted.
|
||||
const audience = (await getPlayerIdsInInstance(c.env.DB, gameSessionId)).filter(
|
||||
(id) => id !== voterId
|
||||
)
|
||||
|
||||
// Every recipient is attempted even if an earlier one fails, so the reachable
|
||||
// players still get the vote.
|
||||
const results = await Promise.all(
|
||||
audience.map((toPlayerId) =>
|
||||
pushVoteToKick(c, {
|
||||
FromPlayerId: voterId,
|
||||
ToPlayerId: toPlayerId,
|
||||
Type: MessageType.VoteToKick,
|
||||
// An escaped JSON STRING, not a nested object — see VoteToKickData.
|
||||
Data: voteToKickData({
|
||||
PlayerId: playerIdField,
|
||||
Response: '',
|
||||
GameSessionId: gameSessionId,
|
||||
}),
|
||||
})
|
||||
)
|
||||
)
|
||||
if (results.includes(false)) {
|
||||
return c.json({ success: false, error: 'Failed to deliver vote' }, 500)
|
||||
}
|
||||
|
||||
return c.json({ success: true, error: '' })
|
||||
}
|
||||
)
|
||||
|
||||
// The kick a room's own staff hand out from the moderation menu: eject named players
|
||||
// from ONE live instance. Two gates, and both matter — the caller must be able to
|
||||
// moderate the room the instance belongs to, and each named player must actually be
|
||||
// standing in that instance. Without the second, a creator could name any account id
|
||||
// and kick a stranger out of somebody else's room.
|
||||
.post(
|
||||
'/api/PlayerReporting/v1/instantKick',
|
||||
describeRoute({
|
||||
tags: ['Moderation'],
|
||||
summary: 'Kick players out of a room instance',
|
||||
description:
|
||||
'Ejects the named players from one live room instance. `GameSessionId` is that ' +
|
||||
'instance (`roomInstanceId`); the body is JSON, unlike the form posts elsewhere in ' +
|
||||
'this controller.\n\n' +
|
||||
'Gated to the instance’s room: the caller must be its creator or hold a role of ' +
|
||||
'Moderator (20) or above on it — anyone else with a valid token gets a 403. ' +
|
||||
'Nobody who can moderate the room can be kicked out of it, and a caller cannot ' +
|
||||
'kick themselves.\n\n' +
|
||||
'A player is only kicked if their live `presence` row puts them in **that** ' +
|
||||
'instance. Anyone else named — offline, or standing in another room — is skipped ' +
|
||||
'in silence, so naming an account id cannot reach into a session the caller has ' +
|
||||
'no authority over.\n\n' +
|
||||
'Each kicked player loses their presence row (they read offline at once and the ' +
|
||||
'instance frees a slot) and gets a `ModerationKick` frame (id 22) — the frame the ' +
|
||||
'client acts on to leave. It is the same frame a room ban sends, but `IsBan` is ' +
|
||||
'false: this only removes them from the session they are in, and nothing stops ' +
|
||||
'them rejoining. The frame is EPHEMERAL — a kick is true of the moment it ' +
|
||||
'happened, and queueing one would eject the player from an unrelated session on ' +
|
||||
'their next connect.\n\n' +
|
||||
'Answers the same lowercase `{ success, error }` envelope the report write uses, ' +
|
||||
'and says nothing about who was actually kicked — the response shape is ' +
|
||||
'unverified against the real service.',
|
||||
security: AUTHED,
|
||||
requestBody: jsonBody(InstantKickRequest, 'The instance and the players to eject'),
|
||||
responses: {
|
||||
200: json(SuccessErrorEnvelope, '`{ success: true, error: "" }`'),
|
||||
400: json(SuccessErrorEnvelope, 'Unparseable body, no `GameSessionId` or no `PlayerIds`'),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
403: json(SuccessErrorEnvelope, 'The caller cannot moderate the instance’s room'),
|
||||
404: json(SuccessErrorEnvelope, 'No such game session'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const moderatorId = await authedId(c)
|
||||
if (moderatorId === null) return unauthorized(c)
|
||||
|
||||
const body = (await c.req.json().catch(() => null)) as {
|
||||
GameSessionId?: unknown
|
||||
PlayerIds?: unknown
|
||||
} | null
|
||||
if (body === null) return c.json({ success: false, error: 'Invalid request body' }, 400)
|
||||
|
||||
const gameSessionId = typeof body.GameSessionId === 'number' ? body.GameSessionId : Number.NaN
|
||||
if (!Number.isInteger(gameSessionId)) {
|
||||
return c.json({ success: false, error: 'GameSessionId is required' }, 400)
|
||||
}
|
||||
const playerIds = Array.isArray(body.PlayerIds)
|
||||
? body.PlayerIds.filter((id): id is number => Number.isInteger(id))
|
||||
: []
|
||||
if (playerIds.length === 0) {
|
||||
return c.json({ success: false, error: 'PlayerIds is required' }, 400)
|
||||
}
|
||||
|
||||
// The instance names the room, and the room carries the roles this is gated on —
|
||||
// a game session with no room behind it can't authorise anything.
|
||||
const instance = await getStoredRoomInstance(c.env.DB, gameSessionId)
|
||||
const room = instance && (await getRoomById(c.env.DB, instance.roomId))
|
||||
if (!room) return c.json({ success: false, error: 'This game session does not exist!' }, 404)
|
||||
if (!canModerateRoom(room, moderatorId)) {
|
||||
return c.json({ success: false, error: 'Forbidden' }, 403)
|
||||
}
|
||||
|
||||
// One read for the batch. A player is kicked only when their LIVE presence puts
|
||||
// them in this very instance: offline, expired or standing elsewhere are all the
|
||||
// same "not here", and are skipped rather than refused — the client sends a list
|
||||
// and one stale id in it must not sink the rest.
|
||||
const presences = await getPresences<{ roomInstanceId?: number }>(c.env.DB, playerIds)
|
||||
const kicked: number[] = []
|
||||
for (const playerId of playerIds) {
|
||||
// The room's own staff are not kickable out of their room — otherwise a
|
||||
// moderator could throw the creator out of it. Nor is the caller themselves.
|
||||
if (playerId === moderatorId || canModerateRoom(room, playerId)) continue
|
||||
if (presences.get(playerId)?.roomInstance?.roomInstanceId !== gameSessionId) continue
|
||||
await deletePresence(c.env.DB, playerId)
|
||||
kicked.push(playerId)
|
||||
}
|
||||
|
||||
if (kicked.length > 0) {
|
||||
// The instance just lost players — recompute its fullness so a full room opens
|
||||
// back up, exactly as the `match` worker does when someone logs out.
|
||||
await refreshInstanceFullness(c.env.DB, gameSessionId)
|
||||
const roomName = typeof room.Name === 'string' ? room.Name : 'this room'
|
||||
await pushInstantKick(c, kicked, gameSessionId, roomName, moderatorId)
|
||||
}
|
||||
|
||||
return c.json({ success: true, error: '' })
|
||||
}
|
||||
)
|
||||
|
||||
// A warning handed down by a moderator — the staff-side counterpart to a report.
|
||||
// Gated on the `moderator` role in the token, not just a valid one.
|
||||
.post(
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
PRESENCE_TTL_SECONDS,
|
||||
PROGRESSION_SCHEMA_DDL,
|
||||
RELATIONSHIP_SCHEMA_DDL,
|
||||
ROOM_INSTANCE_SCHEMA_DDL,
|
||||
ROOM_SCHEMA_DDL,
|
||||
seedRoomWithSubRooms,
|
||||
SUBROOM_SCHEMA_DDL,
|
||||
@@ -63,7 +64,11 @@ import { getWarningsAgainst, SCHEMA_DDL as WARNINGS_SCHEMA_DDL } from '../../war
|
||||
import type { SavedImage } from '@repo/domain'
|
||||
import type { Env } from '../../context'
|
||||
import type { EventTag, PlayerEvent, PlayerEventEnvelope, PlayerEventResult } from '../../events-db'
|
||||
import type { InventionSaveResult, SavedInvention } from '../../inventions-db'
|
||||
import type {
|
||||
InventionSaveResult,
|
||||
InventionSaveV9Result,
|
||||
SavedInvention,
|
||||
} from '../../inventions-db'
|
||||
|
||||
declare module 'cloudflare:test' {
|
||||
interface ProvidedEnv extends Env {}
|
||||
@@ -90,6 +95,19 @@ const TEST_ROOMS = [
|
||||
SubRooms: [{ SubRoomId: 3 }],
|
||||
Roles: [{ AccountId: 42, Role: 30, LastChangedByAccountId: null, InvitedRole: 0 }],
|
||||
},
|
||||
{
|
||||
// The instant kick's room. Owned by account 42 (the default test token); 43 holds
|
||||
// Moderator (20) and 44 only Host (10) — the tier just below that gate.
|
||||
RoomId: 4,
|
||||
Name: 'KickRoom',
|
||||
IsDorm: false,
|
||||
CreatorAccountId: 42,
|
||||
SubRooms: [{ SubRoomId: 4 }],
|
||||
Roles: [
|
||||
{ AccountId: 43, Role: 20, LastChangedByAccountId: null, InvitedRole: 0 },
|
||||
{ AccountId: 44, Role: 10, LastChangedByAccountId: null, InvitedRole: 0 },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -128,6 +146,10 @@ beforeAll(async () => {
|
||||
// Presence (owned by the rooms worker) — the online-friend count joins onto it.
|
||||
for (const stmt of PRESENCE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
|
||||
// Room instances (owned by the rooms worker) — the instant kick resolves the game
|
||||
// session it is given to the room whose staff may kick from it.
|
||||
for (const stmt of ROOM_INSTANCE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
|
||||
// Outfit table (owned by the econ worker) — /outfits/me reads and writes slot 0.
|
||||
for (const stmt of OUTFIT_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
|
||||
@@ -209,10 +231,12 @@ describe('public endpoints', () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/config/v1/amplitude`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
AmplitudeKey: 'a',
|
||||
StatSigKey: 'a',
|
||||
RudderStackKey: 'a',
|
||||
AmplitudeKey: '',
|
||||
UseRudderStack: false,
|
||||
RudderStackKey: '',
|
||||
UseStatSig: false,
|
||||
StatSigKey: '',
|
||||
StatSigEnvironment: 0,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -726,32 +750,6 @@ describe('public endpoints', () => {
|
||||
expect(charadesWordsFor(new Date('2026-04-01T23:59:59Z'))).toBe(april)
|
||||
})
|
||||
|
||||
// The client POSTs this with no body, despite it being a pure read; the route answers
|
||||
// GET as well, and both methods serve the same body.
|
||||
test.each(['GET', 'POST'])(
|
||||
'%s /api/PlayerReporting/v1/moderationBlockDetails reports "not blocked"',
|
||||
async (method) => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`,
|
||||
{ method }
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
// ReportCategory -1 = Unknown (0 is a real category). Message is null, not the
|
||||
// reference stub's empty string — the client tells "no message" from a blank one.
|
||||
expect(await res.json()).toEqual({
|
||||
ReportCategory: -1,
|
||||
Duration: 0,
|
||||
GameSessionId: 0,
|
||||
IsBan: false,
|
||||
IsHostKick: false,
|
||||
IsVoiceModAutoban: false,
|
||||
Message: null,
|
||||
PlayerIdReporter: null,
|
||||
TimeoutStartedAt: null,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// A fixed list, in render order — the client shows the buttons in the order they
|
||||
// arrive, so the order is part of the contract, not just the contents.
|
||||
test('GET /api/PlayerReporting/v1/voteToKickReasons serves the reasons in order', async () => {
|
||||
@@ -1808,6 +1806,701 @@ describe('public endpoints', () => {
|
||||
expect((await one.json()) as SavedInvention).toMatchObject({ InventionId: saved.InventionId })
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v9/save answers the enveloped result the client reads', async () => {
|
||||
const body = {
|
||||
name: '082926 13:42:46',
|
||||
description: 'No description yet',
|
||||
imageName: 'invention/2026-08-29/52c1e282-76f5-4974-975f-d85060884085.jpg',
|
||||
hasBetaContent: false,
|
||||
instantiationCost: 101,
|
||||
lightsCost: 0,
|
||||
chipsCost: 0,
|
||||
cloudVariablesCost: 0,
|
||||
aiCost: 0,
|
||||
ugcVersion: 1,
|
||||
creationRoomId: 398,
|
||||
inventionDataFilename: '2026-08-29/cb608051-f38b-4ef2-aa8a-a26eb0195b2b.inv',
|
||||
referencedInventions: [],
|
||||
referencedUnityAssetIds: [],
|
||||
creatorAccountRole: 255,
|
||||
convertedFromInventionId: null,
|
||||
displayMetadataJson: '{"0":0,"99":0}',
|
||||
longDescription: '',
|
||||
tagsRequest: { AutoTags: ['small'], CustomTags: null },
|
||||
}
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5151')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
|
||||
// v9 is ENVELOPED where v6 is bare. The client checks Success and then reads
|
||||
// Value.Invention.InventionId — unguarded, so a true Success with a null Value is
|
||||
// the one shape that takes it down.
|
||||
const result = (await res.json()) as InventionSaveV9Result
|
||||
expect(result.Success).toBe(true)
|
||||
expect(result.Error).toBeNull()
|
||||
expect(result.error_id).toBeNull()
|
||||
const value = result.Value
|
||||
if (value === null) throw new Error('Value must not be null on a successful save')
|
||||
expect(value.Status).toBe(0)
|
||||
expect(Object.keys(value).sort()).toEqual([
|
||||
'Invention',
|
||||
'InventionVersion',
|
||||
'Status',
|
||||
'TagsResponse',
|
||||
])
|
||||
|
||||
const saved = value.Invention
|
||||
expect(saved.InventionId).toBeGreaterThan(0)
|
||||
expect(saved.CreatorPlayerId).toBe(5151)
|
||||
expect(saved.Name).toBe(body.name)
|
||||
expect(saved.CreationRoomId).toBe(398)
|
||||
expect(saved.DisplayMetadataJson).toBe('{"0":0,"99":0}')
|
||||
// UgcVersion is an INVENTION field here, next to the version numbers — not a
|
||||
// version one, where its twin HasBetaContent lives.
|
||||
expect(saved.UgcVersion).toBe(1)
|
||||
expect(saved.CurrentVersionNumber).toBe(1)
|
||||
expect(saved.LatestVersionNumber).toBe(1)
|
||||
// The v9 RRInvention has no nested version, no Referenced* and no IsPublished —
|
||||
// the client reads publication from FirstPublishedAt.
|
||||
expect(saved).not.toHaveProperty('CurrentVersion')
|
||||
expect(saved).not.toHaveProperty('ReferencedInventions')
|
||||
expect(saved).not.toHaveProperty('IsPublished')
|
||||
expect(saved.FirstPublishedAt).toBeNull()
|
||||
|
||||
// Costs, the blob and the beta flag ride on the version beside it. No AICost: the
|
||||
// request sends one and this DTO has nowhere to put it.
|
||||
expect(value.InventionVersion).toMatchObject({
|
||||
InventionId: saved.InventionId,
|
||||
VersionNumber: 1,
|
||||
InstantiationCost: 101,
|
||||
HasBetaContent: false,
|
||||
BlobName: body.inventionDataFilename,
|
||||
UgcAccessibility: null,
|
||||
ReferencedInventions: [],
|
||||
ReferencedUnityAssetIds: [],
|
||||
})
|
||||
expect(value.InventionVersion).not.toHaveProperty('AICost')
|
||||
|
||||
// The tagsRequest is applied as `v1/settags` would have applied it, and answered
|
||||
// the way settags answers: a result code and the bare tag NAMES.
|
||||
expect(value.TagsResponse).toEqual({ Result: 0, Tags: ['small'] })
|
||||
const details = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1/details?inventionId=${saved.InventionId}`
|
||||
)
|
||||
expect(await details.json()).toEqual({ Tags: [{ Tag: 'small', Type: 2 }] })
|
||||
|
||||
// Stored once, read by every version: the older lookup still serves the record it
|
||||
// always did, nested CurrentVersion and all.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${saved.InventionId}`
|
||||
)
|
||||
expect((await one.json()) as SavedInvention).toMatchObject({
|
||||
InventionId: saved.InventionId,
|
||||
IsPublished: false,
|
||||
CurrentVersion: { BlobName: body.inventionDataFilename },
|
||||
})
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v9/save leaves the v9-only keys off the stored record', async () => {
|
||||
// The same body a v6 client sends, posted at v9: nothing is back-filled, so the
|
||||
// record is the one v6 has always stored. The response still carries the full v9
|
||||
// projection — those fields have defaults there, not absences.
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5152')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Bare Save', inventionDataFilename: 'bare.inv' }),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
const value = ((await res.json()) as InventionSaveV9Result).Value
|
||||
if (value === null) throw new Error('Value must not be null on a successful save')
|
||||
expect(value.Invention.UgcVersion).toBe(0)
|
||||
expect(value.Invention.DisplayMetadataJson).toBeNull()
|
||||
// A save always mints a version; the key is nullable only because econ's
|
||||
// `v3/buyInvention` answers in this same envelope and a buy mints none.
|
||||
expect(value.InventionVersion).not.toBeNull()
|
||||
expect(value.InventionVersion?.HasBetaContent).toBe(false)
|
||||
expect(value.TagsResponse).toEqual({ Result: 0, Tags: [] })
|
||||
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${value.Invention.InventionId}`
|
||||
)
|
||||
const stored = (await one.json()) as SavedInvention
|
||||
expect(stored).not.toHaveProperty('Tags')
|
||||
expect(stored).not.toHaveProperty('UgcVersion')
|
||||
expect(stored).not.toHaveProperty('ReferencedUnityAssetIds')
|
||||
expect(stored.CurrentVersion).not.toHaveProperty('HasBetaContent')
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v9/save refuses through the envelope, never a bare error', async () => {
|
||||
// A refusal the client can show is Success:false with a null Value — the branch
|
||||
// that reads Error and nothing else. A bare `{ error }` body would deserialize to
|
||||
// a null envelope and take the client down instead of failing the save.
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5153')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'No Blob' }),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
Value: null,
|
||||
Success: false,
|
||||
Error: 'inventionDataFilename is required',
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
// Even the 401 answers the envelope: an empty body is a null envelope to the
|
||||
// client, which is the crash, not a refusal.
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Anon', inventionDataFilename: 'anon.inv' }),
|
||||
})
|
||||
expect(anon.status).toBe(401)
|
||||
expect((await anon.json()) as InventionSaveV9Result).toMatchObject({
|
||||
Value: null,
|
||||
Success: false,
|
||||
})
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v9/save keeps the save when a tag breaks the tag rule', async () => {
|
||||
// The reply carries a tag result of its own, so the two outcomes are separate: a
|
||||
// hyphen in a tag must not cost the player the build they just saved.
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5154')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Tagged Badly',
|
||||
inventionDataFilename: 'tagged-badly.inv',
|
||||
tagsRequest: { AutoTags: ['small'], CustomTags: ['bad-tag'] },
|
||||
}),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
const result = (await res.json()) as InventionSaveV9Result
|
||||
expect(result.Success).toBe(true)
|
||||
const value = result.Value
|
||||
if (value === null) throw new Error('a refused tag must not refuse the save')
|
||||
expect(value.Invention.InventionId).toBeGreaterThan(0)
|
||||
|
||||
// Non-zero result, and the whole list dropped rather than the offending tag alone —
|
||||
// the creator re-submits it through `v1/settags` and sees what took.
|
||||
expect(value.TagsResponse).toEqual({ Result: 1, Tags: [] })
|
||||
const details = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1/details?inventionId=${value.Invention.InventionId}`
|
||||
)
|
||||
expect(await details.json()).toEqual({ Tags: [] })
|
||||
|
||||
// The invention is on the creator's shelf regardless.
|
||||
const mine = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/mine`, {
|
||||
headers: await bearer('5154'),
|
||||
})
|
||||
expect(((await mine.json()) as SavedInvention[]).map((i) => i.InventionId)).toEqual([
|
||||
value.Invention.InventionId,
|
||||
])
|
||||
})
|
||||
|
||||
test('PUT /api/inventions/v2/metadata edits only the fields that aren’t null', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5160')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Before Edit',
|
||||
description: 'the original description',
|
||||
imageName: 'invention/before.jpg',
|
||||
inventionDataFilename: 'before-edit.inv',
|
||||
longDescription: 'the original blurb',
|
||||
tagsRequest: { AutoTags: ['small'], CustomTags: null },
|
||||
}),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
expect(inventionId).toBeGreaterThan(0)
|
||||
|
||||
// The client sends the whole shape every time and marks what it isn't touching as
|
||||
// null — so a null Name must not blank the name.
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('5160')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
InventionId: inventionId,
|
||||
Name: null,
|
||||
Description: 'devin test No description yet',
|
||||
LongDescription: null,
|
||||
ImageName: null,
|
||||
TagsRequest: null,
|
||||
}),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
const result = (await res.json()) as InventionSaveV9Result
|
||||
expect(result.Success).toBe(true)
|
||||
const value = result.Value
|
||||
if (value === null) throw new Error('Value must not be null on a successful edit')
|
||||
|
||||
// The edit answers the UPDATED invention — the client re-renders the detail page
|
||||
// from it — in the same envelope the save answers.
|
||||
expect(value.Invention.Description).toBe('devin test No description yet')
|
||||
expect(value.Invention.Name).toBe('Before Edit')
|
||||
expect(value.Invention.ImageName).toBe('invention/before.jpg')
|
||||
expect(value.Invention.InventionId).toBe(inventionId)
|
||||
// A null TagsRequest leaves the stored tags alone, and they are still reported: the
|
||||
// list is what the invention HAS, not what this call changed.
|
||||
expect(value.TagsResponse).toEqual({ Result: 0, Tags: ['small'] })
|
||||
|
||||
// And it stuck — including the long description, which the v9 Invention DTO has no
|
||||
// key for but the record keeps.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${inventionId}`
|
||||
)
|
||||
expect((await one.json()) as SavedInvention).toMatchObject({
|
||||
Name: 'Before Edit',
|
||||
Description: 'devin test No description yet',
|
||||
LongDescription: 'the original blurb',
|
||||
Tags: [{ Tag: 'small', Type: 2 }],
|
||||
})
|
||||
})
|
||||
|
||||
test('PUT /api/inventions/v2/metadata treats an empty string as a clear, not a null', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5161')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Clear Me',
|
||||
description: 'to be cleared',
|
||||
imageName: 'invention/clear-me.jpg',
|
||||
inventionDataFilename: 'clear-me.inv',
|
||||
longDescription: 'blurb to be cleared',
|
||||
}),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('5161')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
InventionId: inventionId,
|
||||
Name: null,
|
||||
Description: '',
|
||||
LongDescription: '',
|
||||
ImageName: '',
|
||||
TagsRequest: { AutoTags: ['large'], CustomTags: ['puzzle'] },
|
||||
}),
|
||||
})
|
||||
const value = ((await res.json()) as InventionSaveV9Result).Value
|
||||
if (value === null) throw new Error('Value must not be null on a successful edit')
|
||||
expect(value.Invention.Description).toBe('')
|
||||
expect(value.Invention.ImageName).toBe('')
|
||||
// TagsRequest replaces both lists wholesale, auto first, then custom.
|
||||
expect(value.TagsResponse).toEqual({ Result: 0, Tags: ['large', 'puzzle'] })
|
||||
|
||||
// An empty name is not how a name is cleared — nothing can draw a nameless
|
||||
// invention, so it fails the same rule a save holds it to and nothing is written.
|
||||
const named = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('5161')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId, Name: '' }),
|
||||
})
|
||||
expect(named.status).toBe(200)
|
||||
expect((await named.json()) as InventionSaveV9Result).toMatchObject({
|
||||
Value: null,
|
||||
Success: false,
|
||||
})
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${inventionId}`
|
||||
)
|
||||
expect(((await one.json()) as SavedInvention).Name).toBe('Clear Me')
|
||||
})
|
||||
|
||||
test('PUT /api/inventions/v2/metadata refuses another creator’s invention in-band', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5162')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Not Yours', inventionDataFilename: 'not-yours.inv' }),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
|
||||
// Someone else's invention and an unknown one are domain answers, not transport
|
||||
// ones — the client's own status enum has NotCreator and DoesNotExist members — so
|
||||
// they come back 200 in the envelope, where the message reaches a human.
|
||||
const theirs = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('5163')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId, Description: 'hijacked' }),
|
||||
})
|
||||
expect(theirs.status).toBe(200)
|
||||
expect(await theirs.json()).toEqual({
|
||||
Value: null,
|
||||
Success: false,
|
||||
Error: 'Not your invention',
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
const missing = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('5162')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: 987654, Description: 'nobody' }),
|
||||
})
|
||||
expect(missing.status).toBe(200)
|
||||
expect((await missing.json()) as InventionSaveV9Result).toMatchObject({
|
||||
Value: null,
|
||||
Error: 'No such invention',
|
||||
})
|
||||
|
||||
// A missing token is the one refusal that stays a transport failure — but it still
|
||||
// answers the envelope, because an unparseable body crashes the client.
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId, Description: 'anon' }),
|
||||
})
|
||||
expect(anon.status).toBe(401)
|
||||
expect((await anon.json()) as InventionSaveV9Result).toMatchObject({
|
||||
Value: null,
|
||||
Success: false,
|
||||
})
|
||||
|
||||
// Untouched throughout.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${inventionId}`
|
||||
)
|
||||
expect(((await one.json()) as SavedInvention).Description).toBe('No description yet')
|
||||
})
|
||||
|
||||
test('PUT /api/inventions/v2/metadata keeps the edit when a tag breaks the tag rule', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5164')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Tag Trouble',
|
||||
inventionDataFilename: 'tag-trouble.inv',
|
||||
tagsRequest: { AutoTags: ['small'], CustomTags: null },
|
||||
}),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('5164')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
InventionId: inventionId,
|
||||
Description: 'edited anyway',
|
||||
TagsRequest: { AutoTags: ['small'], CustomTags: ['bad-tag'] },
|
||||
}),
|
||||
})
|
||||
const value = ((await res.json()) as InventionSaveV9Result).Value
|
||||
if (value === null) throw new Error('a refused tag must not refuse the edit')
|
||||
// The metadata edit lands; the tags are what didn't.
|
||||
expect(value.Invention.Description).toBe('edited anyway')
|
||||
expect(value.TagsResponse).toEqual({ Result: 1, Tags: [] })
|
||||
const details = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1/details?inventionId=${inventionId}`
|
||||
)
|
||||
expect(await details.json()).toEqual({ Tags: [] })
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v4/publish publishes with the permission and accessibility sent', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5170')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Publish Me', inventionDataFilename: 'publish-me.inv' }),
|
||||
})
|
||||
const saved = ((await save.json()) as InventionSaveV9Result).Value?.Invention
|
||||
expect(saved?.FirstPublishedAt).toBeNull()
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v4/publish`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5170')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
InventionId: saved?.InventionId,
|
||||
Permission: 20,
|
||||
Accessibility: 1,
|
||||
Price: null,
|
||||
}),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
const result = (await res.json()) as InventionSaveV9Result
|
||||
expect(result.Success).toBe(true)
|
||||
const value = result.Value
|
||||
if (value === null) throw new Error('Value must not be null on a successful publish')
|
||||
|
||||
// Publishing narrows what everyone else gets down to what the sheet sent, and dates
|
||||
// the invention — the client reads publication from FirstPublishedAt, not a flag.
|
||||
expect(value.Invention.GeneralPermission).toBe(20)
|
||||
expect(value.Invention.Accessibility).toBe(1)
|
||||
expect(typeof value.Invention.FirstPublishedAt).toBe('string')
|
||||
expect(value.Invention.Price).toBe(0)
|
||||
|
||||
// And it's findable now: the record the older reads serve says published, and it
|
||||
// turns up in search.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${value.Invention.InventionId}`
|
||||
)
|
||||
expect((await one.json()) as SavedInvention).toMatchObject({ IsPublished: true })
|
||||
const found = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/search?value=Publish Me`)
|
||||
expect(((await found.json()) as SavedInvention[]).map((i) => i.InventionId)).toContain(
|
||||
value.Invention.InventionId
|
||||
)
|
||||
|
||||
// Taken back out: the browse tests below assert the exact published catalogue, and
|
||||
// a test that publishes something publicly is a test that changes it.
|
||||
await env.DB.prepare('DELETE FROM invention WHERE id = ?1')
|
||||
.bind(value.Invention.InventionId)
|
||||
.run()
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v4/publish keeps an unlisted invention out of the feeds', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5171')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Quietly Published',
|
||||
inventionDataFilename: 'quietly-published.inv',
|
||||
creationRoomId: 4171,
|
||||
}),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v4/publish`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5171')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId, Permission: 20, Accessibility: 2 }),
|
||||
})
|
||||
const value = ((await res.json()) as InventionSaveV9Result).Value
|
||||
expect(value?.Invention.Accessibility).toBe(2)
|
||||
|
||||
// Unlisted is published — it is reachable by id, which is the whole point of it —
|
||||
// but it is not something anyone comes across.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${inventionId}`
|
||||
)
|
||||
expect((await one.json()) as SavedInvention).toMatchObject({
|
||||
InventionId: inventionId,
|
||||
IsPublished: true,
|
||||
})
|
||||
const found = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v2/search?value=Quietly Published`
|
||||
)
|
||||
expect(await found.json()).toEqual([])
|
||||
const room = await exports.default.fetch(`${ORIGIN}/api/inventions/v1/room?id=4171`)
|
||||
expect(await room.json()).toEqual([])
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v4/publish leaves a price and a first-publish date alone', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5172')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'For Sale', inventionDataFilename: 'for-sale.inv' }),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
|
||||
const publish = async (body: Record<string, unknown>) => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v4/publish`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5172')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId, ...body }),
|
||||
})
|
||||
const value = ((await res.json()) as InventionSaveV9Result).Value
|
||||
if (value === null) throw new Error('Value must not be null on a successful publish')
|
||||
return value.Invention
|
||||
}
|
||||
|
||||
const first = await publish({ Permission: 80, Accessibility: 1, Price: 250 })
|
||||
expect(first.Price).toBe(250)
|
||||
|
||||
// A republish that says nothing about money must not give away something that was
|
||||
// for sale, and must not re-date the first publish.
|
||||
const again = await publish({ Permission: 20, Accessibility: 1, Price: null })
|
||||
expect(again.Price).toBe(250)
|
||||
expect(again.GeneralPermission).toBe(20)
|
||||
expect(again.FirstPublishedAt).toBe(first.FirstPublishedAt)
|
||||
|
||||
// A negative price is dropped rather than stored.
|
||||
expect((await publish({ Price: -5 })).Price).toBe(250)
|
||||
|
||||
// Out of the published catalogue again — see the note in the publish test above.
|
||||
await env.DB.prepare('DELETE FROM invention WHERE id = ?1').bind(inventionId).run()
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v4/publish refuses another creator’s invention in-band', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5173')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Theirs Alone', inventionDataFilename: 'theirs-alone.inv' }),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
|
||||
const theirs = await exports.default.fetch(`${ORIGIN}/api/inventions/v4/publish`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5174')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId, Permission: 20, Accessibility: 1 }),
|
||||
})
|
||||
expect(theirs.status).toBe(200)
|
||||
expect(await theirs.json()).toEqual({
|
||||
Value: null,
|
||||
Success: false,
|
||||
Error: 'Not your invention',
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/inventions/v4/publish`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId, Permission: 20, Accessibility: 1 }),
|
||||
})
|
||||
expect(anon.status).toBe(401)
|
||||
expect((await anon.json()) as InventionSaveV9Result).toMatchObject({ Value: null })
|
||||
|
||||
// Still unpublished throughout.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${inventionId}`
|
||||
)
|
||||
expect((await one.json()) as SavedInvention).toMatchObject({
|
||||
IsPublished: false,
|
||||
FirstPublishedAt: null,
|
||||
})
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v2/delete removes the creator’s invention', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5180')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: 'Delete Me',
|
||||
inventionDataFilename: 'delete-me.inv',
|
||||
tagsRequest: { AutoTags: ['small'], CustomTags: null },
|
||||
}),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
expect(inventionId).toBeGreaterThan(0)
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/delete`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5180')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId }),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
// `Value` is null even on success — there is no invention left to redraw from.
|
||||
expect(await res.json()).toEqual({ Value: null, Success: true, Error: null, error_id: null })
|
||||
|
||||
// Gone from the read and from the creator's shelf.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${inventionId}`
|
||||
)
|
||||
expect(one.status).toBe(404)
|
||||
const mine = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/mine`, {
|
||||
headers: await bearer('5180'),
|
||||
})
|
||||
expect(((await mine.json()) as SavedInvention[]).map((i) => i.InventionId)).not.toContain(
|
||||
inventionId
|
||||
)
|
||||
|
||||
// And the row itself, tags and all, rather than a hidden record still taking the id.
|
||||
const row = await env.DB.prepare('SELECT COUNT(*) AS n FROM invention WHERE id = ?1')
|
||||
.bind(inventionId)
|
||||
.first<{ n: number }>()
|
||||
expect(row?.n).toBe(0)
|
||||
|
||||
// Deleting it twice is a refusal, not a second success: the id resolves to nothing.
|
||||
const again = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/delete`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5180')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId }),
|
||||
})
|
||||
expect(await again.json()).toEqual({
|
||||
Value: null,
|
||||
Success: false,
|
||||
Error: 'No such invention',
|
||||
error_id: null,
|
||||
})
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v2/delete refuses anyone but the creator, in-band', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5181')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Not Yours To Bin', inventionDataFilename: 'not-yours.inv' }),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention.InventionId
|
||||
|
||||
// 5182 BOUGHT it — owning a copy is still not the right to delete it.
|
||||
await grantInvention(env.DB, 5182, inventionId as number)
|
||||
const theirs = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/delete`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5182')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId }),
|
||||
})
|
||||
expect(theirs.status).toBe(200)
|
||||
expect(await theirs.json()).toEqual({
|
||||
Value: null,
|
||||
Success: false,
|
||||
Error: 'Not your invention',
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
const missing = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/delete`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5181')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: 987_655 }),
|
||||
})
|
||||
expect(missing.status).toBe(200)
|
||||
expect(await missing.json()).toEqual({
|
||||
Value: null,
|
||||
Success: false,
|
||||
Error: 'No such invention',
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
// A missing token is the one refusal that stays a transport failure — and it still
|
||||
// answers the envelope rather than a bare error body.
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/delete`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId }),
|
||||
})
|
||||
expect(anon.status).toBe(401)
|
||||
expect(await anon.json()).toMatchObject({ Value: null, Success: false })
|
||||
|
||||
// Still there throughout.
|
||||
const one = await exports.default.fetch(
|
||||
`${ORIGIN}/api/inventions/v1?inventionId=${inventionId}`
|
||||
)
|
||||
expect(one.status).toBe(200)
|
||||
})
|
||||
|
||||
test('POST /api/inventions/v2/delete leaves a buyer’s ownership row behind', async () => {
|
||||
const save = await exports.default.fetch(`${ORIGIN}/api/inventions/v9/save`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5183')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Sold Then Binned', inventionDataFilename: 'sold.inv' }),
|
||||
})
|
||||
const inventionId = ((await save.json()) as InventionSaveV9Result).Value?.Invention
|
||||
.InventionId as number
|
||||
await grantInvention(env.DB, 5184, inventionId)
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/delete`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('5183')), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: inventionId }),
|
||||
})
|
||||
expect((await res.json()) as { Success: boolean }).toMatchObject({ Success: true })
|
||||
|
||||
// The purchase record is not rewritten by someone else's delete...
|
||||
const owned = await env.DB.prepare(
|
||||
'SELECT COUNT(*) AS n FROM inventory_invention WHERE invention_id = ?1'
|
||||
)
|
||||
.bind(inventionId)
|
||||
.first<{ n: number }>()
|
||||
expect(owned?.n).toBe(1)
|
||||
|
||||
// ...but with no invention row behind it, it drops out of the buyer's shelf anyway.
|
||||
const mine = await exports.default.fetch(`${ORIGIN}/api/inventions/v2/mine`, {
|
||||
headers: await bearer('5184'),
|
||||
})
|
||||
expect(((await mine.json()) as SavedInvention[]).map((i) => i.InventionId)).not.toContain(
|
||||
inventionId
|
||||
)
|
||||
})
|
||||
|
||||
test('GET /api/inventions/v2/mine lists bought inventions alongside the caller’s own', async () => {
|
||||
// Account 6100 creates one; 6101 buys it (the econ worker's buyInvention writes
|
||||
// exactly this row) and also creates one of their own.
|
||||
@@ -3286,6 +3979,374 @@ describe('custom avatar items', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('instant kick', () => {
|
||||
// The game session the kick names, and one belonging to the same room that must be
|
||||
// left out of it.
|
||||
const SESSION = 1013781
|
||||
const OTHER_SESSION = 1013782
|
||||
|
||||
const hub = () => env.RECFLARE_NOTIFICATIONS_HUB.getByName('global')
|
||||
|
||||
// Room instances are written by the `match` worker; seeded straight into the table
|
||||
// here, the way the presence rows below are.
|
||||
const seedInstance = async (roomInstanceId: number, maxCapacity = 0, isFull = false) =>
|
||||
env.DB.prepare('INSERT OR REPLACE INTO room_instance (data) VALUES (?1)')
|
||||
.bind(
|
||||
JSON.stringify({
|
||||
roomInstanceId,
|
||||
ownerAccountId: 42,
|
||||
roomId: 4,
|
||||
subRoomId: 4,
|
||||
location: '',
|
||||
dataBlob: '',
|
||||
eventId: 0,
|
||||
photonRegionId: 'us',
|
||||
photonRoomId: `photon-${roomInstanceId}`,
|
||||
name: '',
|
||||
maxCapacity,
|
||||
isFull,
|
||||
isPrivate: false,
|
||||
isInProgress: false,
|
||||
roomCode: '',
|
||||
roomInstanceType: 0,
|
||||
clubId: 0,
|
||||
EncryptVoiceChat: false,
|
||||
matchmakingPolicy: 0,
|
||||
allowNewUsers: true,
|
||||
joinDisabled: false,
|
||||
gameVersion: GAME_VERSION,
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
)
|
||||
.run()
|
||||
|
||||
const standIn = async (accountId: number, roomInstanceId: number) =>
|
||||
env.DB.prepare('INSERT OR REPLACE INTO presence (data) VALUES (?1)')
|
||||
.bind(
|
||||
JSON.stringify({
|
||||
accountId,
|
||||
roomInstance: { roomInstanceId, roomId: 4 },
|
||||
statusVisibility: 0,
|
||||
deviceClass: 0,
|
||||
vrMovementMode: 0,
|
||||
platform: 0,
|
||||
appVersion: GAME_VERSION,
|
||||
expiresAt: Math.floor(Date.now() / 1000) + PRESENCE_TTL_SECONDS,
|
||||
})
|
||||
)
|
||||
.run()
|
||||
|
||||
const isPresent = async (accountId: number) =>
|
||||
(await env.DB.prepare('SELECT COUNT(*) AS n FROM presence WHERE account_id = ?1')
|
||||
.bind(accountId)
|
||||
.first<{ n: number }>())!.n === 1
|
||||
|
||||
const isFull = async (roomInstanceId: number) =>
|
||||
(await env.DB.prepare('SELECT is_full AS full FROM room_instance WHERE id = ?1')
|
||||
.bind(roomInstanceId)
|
||||
.first<{ full: number }>())!.full === 1
|
||||
|
||||
const kick = async (body: unknown, sub = '42') =>
|
||||
exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v1/instantKick`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer(sub)), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
const frames = async () =>
|
||||
(await (await hub().fetch('http://do/all')).json()) as Array<{
|
||||
playerIds?: number[]
|
||||
ephemeral?: boolean
|
||||
notificationType: number
|
||||
data: Record<string, unknown>
|
||||
}>
|
||||
|
||||
test('the room’s creator kicks a player out of the session they name', async () => {
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
// A full two-player instance: 205 is kicked, 206 stays.
|
||||
await seedInstance(SESSION, 2, true)
|
||||
await standIn(205, SESSION)
|
||||
await standIn(206, SESSION)
|
||||
|
||||
const res = await kick({ GameSessionId: SESSION, PlayerIds: [205] })
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ success: true, error: '' })
|
||||
|
||||
// Presence is deleted, so they read offline at once — and only theirs is.
|
||||
expect(await isPresent(205)).toBe(false)
|
||||
expect(await isPresent(206)).toBe(true)
|
||||
// The instance lost a player, so it is no longer full.
|
||||
expect(await isFull(SESSION)).toBe(false)
|
||||
|
||||
// One EPHEMERAL ModerationKick, addressed to the kicked player only. `IsBan` is
|
||||
// false — this ejects them from the session and nothing more.
|
||||
expect(await frames()).toEqual([
|
||||
{
|
||||
playerIds: [205],
|
||||
ephemeral: true,
|
||||
notificationType: 22, // NotificationType.ModerationKick
|
||||
data: {
|
||||
ReportCategory: -1, // KickReportCategory.Moderator
|
||||
Duration: 0,
|
||||
GameSessionId: SESSION,
|
||||
IsHostKick: true,
|
||||
Message: 'You have been kicked from KickRoom.',
|
||||
PlayerIdReporter: 42,
|
||||
IsBan: false,
|
||||
IsVoiceModAutoban: false,
|
||||
IsWarning: false,
|
||||
VoteKickReason: '',
|
||||
TimeoutStartedAt: null,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
// The gate that stops a creator kicking a stranger out of somebody else's session by
|
||||
// naming their account id.
|
||||
test('a player who is not in that session is skipped in silence', async () => {
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
await seedInstance(SESSION)
|
||||
await seedInstance(OTHER_SESSION)
|
||||
await standIn(207, OTHER_SESSION)
|
||||
|
||||
// 207 stands in another instance; 208 is offline entirely.
|
||||
const res = await kick({ GameSessionId: SESSION, PlayerIds: [207, 208] })
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ success: true, error: '' })
|
||||
expect(await isPresent(207)).toBe(true)
|
||||
expect(await frames()).toEqual([])
|
||||
})
|
||||
|
||||
test('a room moderator may kick; a host, a stranger and no token may not', async () => {
|
||||
await seedInstance(SESSION)
|
||||
await standIn(209, SESSION)
|
||||
|
||||
// 43 holds Moderator (20) on the room.
|
||||
expect((await kick({ GameSessionId: SESSION, PlayerIds: [209] }, '43')).status).toBe(200)
|
||||
expect(await isPresent(209)).toBe(false)
|
||||
|
||||
await standIn(209, SESSION)
|
||||
// 44 is only a Host (10), and 99 holds nothing at all.
|
||||
for (const sub of ['44', '99']) {
|
||||
const res = await kick({ GameSessionId: SESSION, PlayerIds: [209] }, sub)
|
||||
expect(res.status, sub).toBe(403)
|
||||
expect(await res.json()).toEqual({ success: false, error: 'Forbidden' })
|
||||
}
|
||||
expect(await isPresent(209)).toBe(true)
|
||||
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v1/instantKick`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ GameSessionId: SESSION, PlayerIds: [209] }),
|
||||
})
|
||||
expect(anon.status).toBe(401)
|
||||
})
|
||||
|
||||
// Otherwise a moderator could throw the room's own creator out of it.
|
||||
test('the room’s staff — and the caller — cannot be kicked', async () => {
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
await seedInstance(SESSION)
|
||||
await standIn(42, SESSION)
|
||||
await standIn(43, SESSION)
|
||||
|
||||
// 43 (a moderator) names the creator, a fellow moderator and themselves.
|
||||
const res = await kick({ GameSessionId: SESSION, PlayerIds: [42, 43] }, '43')
|
||||
expect(res.status).toBe(200)
|
||||
expect(await isPresent(42)).toBe(true)
|
||||
expect(await isPresent(43)).toBe(true)
|
||||
expect(await frames()).toEqual([])
|
||||
})
|
||||
|
||||
test('an unknown session 404s, and the body must name a session and players', async () => {
|
||||
await seedInstance(SESSION)
|
||||
const unknown = await kick({ GameSessionId: 999999, PlayerIds: [205] })
|
||||
expect(unknown.status).toBe(404)
|
||||
expect(await unknown.json()).toEqual({
|
||||
success: false,
|
||||
error: 'This game session does not exist!',
|
||||
})
|
||||
|
||||
for (const [body, error] of [
|
||||
[{ PlayerIds: [205] }, 'GameSessionId is required'],
|
||||
[{ GameSessionId: 'nope', PlayerIds: [205] }, 'GameSessionId is required'],
|
||||
[{ GameSessionId: SESSION }, 'PlayerIds is required'],
|
||||
[{ GameSessionId: SESSION, PlayerIds: [] }, 'PlayerIds is required'],
|
||||
[{ GameSessionId: SESSION, PlayerIds: ['205'] }, 'PlayerIds is required'],
|
||||
] as Array<[unknown, string]>) {
|
||||
const res = await kick(body)
|
||||
expect(res.status, error).toBe(400)
|
||||
expect(await res.json()).toEqual({ success: false, error })
|
||||
}
|
||||
|
||||
// A body that isn't JSON at all is the same shape, not a crash.
|
||||
const broken = await exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v1/instantKick`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer()), 'Content-Type': 'application/json' },
|
||||
body: 'not json',
|
||||
})
|
||||
expect(broken.status).toBe(400)
|
||||
expect(await broken.json()).toEqual({ success: false, error: 'Invalid request body' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('vote to kick', () => {
|
||||
// Two live sessions, so a vote called in one can be checked against a player in the
|
||||
// other. Nothing reads `room_instance` here — the gate is presence alone.
|
||||
const SESSION = 1014079
|
||||
const OTHER_SESSION = 1014080
|
||||
|
||||
const hub = () => env.RECFLARE_NOTIFICATIONS_HUB.getByName('global')
|
||||
|
||||
const standIn = async (accountId: number, roomInstanceId: number) =>
|
||||
env.DB.prepare('INSERT OR REPLACE INTO presence (data) VALUES (?1)')
|
||||
.bind(
|
||||
JSON.stringify({
|
||||
accountId,
|
||||
roomInstance: { roomInstanceId, roomId: 4 },
|
||||
statusVisibility: 0,
|
||||
deviceClass: 0,
|
||||
vrMovementMode: 0,
|
||||
platform: 0,
|
||||
appVersion: GAME_VERSION,
|
||||
expiresAt: Math.floor(Date.now() / 1000) + PRESENCE_TTL_SECONDS,
|
||||
})
|
||||
)
|
||||
.run()
|
||||
|
||||
// The body the client posts: `PlayerId=205&Response=True&Reason=…&GameSessionId=…`.
|
||||
const vote = async (fields: Record<string, string>, sub = '42') =>
|
||||
exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v3/voteToKick`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer(sub)), 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams(fields),
|
||||
})
|
||||
|
||||
const frames = async () =>
|
||||
(await (await hub().fetch('http://do/all')).json()) as Array<{
|
||||
playerId?: number
|
||||
ephemeral?: boolean
|
||||
notificationType: number
|
||||
data: Record<string, unknown>
|
||||
}>
|
||||
|
||||
const FIELDS = {
|
||||
PlayerId: '205',
|
||||
Response: 'True',
|
||||
Reason: 'Inactive in games (AFK)',
|
||||
GameSessionId: String(SESSION),
|
||||
}
|
||||
|
||||
test('the vote goes to everyone in the session except the caller', async () => {
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
// 42 calls the vote, 205 is voted on, 206 is a bystander; 207 stands elsewhere.
|
||||
await standIn(42, SESSION)
|
||||
await standIn(205, SESSION)
|
||||
await standIn(206, SESSION)
|
||||
await standIn(207, OTHER_SESSION)
|
||||
|
||||
const res = await vote(FIELDS)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ success: true, error: '' })
|
||||
|
||||
// One frame each for 205 and 206 — the player voted on gets it too (the vote is
|
||||
// called in front of them), the caller does not, and 207 is in another session.
|
||||
// `Data` is an ESCAPED JSON STRING, not a nested object: an object there fails the
|
||||
// client's decoder (`expected:'String Begin Token', actual:'{'`) and takes the whole
|
||||
// notification with it. `PlayerId` inside it is a STRING, as the reference relays it,
|
||||
// and `Response` is empty because the frame is the question, not an answer.
|
||||
const message = {
|
||||
ephemeral: true,
|
||||
notificationType: 2, // NotificationType.MessageReceived
|
||||
data: {
|
||||
FromPlayerId: 42,
|
||||
Type: MessageType.VoteToKick,
|
||||
Data: `{"PlayerId":"205","Response":"","GameSessionId":${SESSION}}`,
|
||||
},
|
||||
}
|
||||
const sent = await frames()
|
||||
expect(sent).toHaveLength(2)
|
||||
expect(sent).toContainEqual({
|
||||
...message,
|
||||
playerId: 205,
|
||||
data: { ...message.data, ToPlayerId: 205 },
|
||||
})
|
||||
expect(sent).toContainEqual({
|
||||
...message,
|
||||
playerId: 206,
|
||||
data: { ...message.data, ToPlayerId: 206 },
|
||||
})
|
||||
})
|
||||
|
||||
test('both players have to be standing in the session', async () => {
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
await standIn(42, OTHER_SESSION)
|
||||
await standIn(205, SESSION)
|
||||
|
||||
// The caller is somewhere else — a vote can't be called into a session you're not in.
|
||||
const away = await vote(FIELDS)
|
||||
expect(away.status).toBe(403)
|
||||
expect(await away.json()).toEqual({
|
||||
success: false,
|
||||
error: 'You are not in that game session!',
|
||||
})
|
||||
|
||||
// And with the caller present, the player voted on has to be there too — offline,
|
||||
// or standing elsewhere, both refuse.
|
||||
await standIn(42, SESSION)
|
||||
await standIn(205, OTHER_SESSION)
|
||||
const elsewhere = await vote(FIELDS)
|
||||
expect(elsewhere.status).toBe(403)
|
||||
expect(await elsewhere.json()).toEqual({
|
||||
success: false,
|
||||
error: 'That player is not in that game session!',
|
||||
})
|
||||
expect((await vote({ ...FIELDS, PlayerId: '208' })).status).toBe(403)
|
||||
|
||||
// Nothing was put to the room on any of those.
|
||||
expect(await frames()).toEqual([])
|
||||
})
|
||||
|
||||
test('the body must name a player and a session, and the call needs a token', async () => {
|
||||
await standIn(42, SESSION)
|
||||
await standIn(205, SESSION)
|
||||
|
||||
for (const [fields, error] of [
|
||||
[{ Response: 'True', GameSessionId: String(SESSION) }, 'PlayerId is required'],
|
||||
[{ PlayerId: 'nope', GameSessionId: String(SESSION) }, 'PlayerId is required'],
|
||||
[{ PlayerId: '205', Response: 'True' }, 'GameSessionId is required'],
|
||||
[{ PlayerId: '205', GameSessionId: 'nope' }, 'GameSessionId is required'],
|
||||
] as Array<[Record<string, string>, string]>) {
|
||||
const res = await vote(fields)
|
||||
expect(res.status, error).toBe(400)
|
||||
expect(await res.json()).toEqual({ success: false, error })
|
||||
}
|
||||
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v3/voteToKick`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams(FIELDS),
|
||||
})
|
||||
expect(anon.status).toBe(401)
|
||||
})
|
||||
|
||||
// A vote called with nobody else there is a no-op rather than an error: the caller and
|
||||
// the player voted on are both here, so the gate passes, and there is simply no room
|
||||
// to put it to.
|
||||
test('a session holding only the two of them sends nothing', async () => {
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
await standIn(42, SESSION)
|
||||
await standIn(205, SESSION)
|
||||
await env.DB.prepare('DELETE FROM presence WHERE account_id NOT IN (42, 205)').run()
|
||||
|
||||
const res = await vote(FIELDS)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ success: true, error: '' })
|
||||
// 205 is still in the session, so they still hear it — only the caller is dropped.
|
||||
expect((await frames()).map((f) => f.playerId)).toEqual([205])
|
||||
})
|
||||
})
|
||||
|
||||
describe('player reports', () => {
|
||||
const submit = async (fields: Record<string, string>, headers?: Record<string, string>) =>
|
||||
exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v3/create`, {
|
||||
@@ -3444,6 +4505,111 @@ describe('player reports', () => {
|
||||
test('banFromReport returns null for an unknown report', async () => {
|
||||
expect(await banFromReport(env.DB, 999_999)).toBeNull()
|
||||
})
|
||||
|
||||
// What the banned player is TOLD. The block screen reads this; it's the same row
|
||||
// matchmake and login refuse on, described rather than merely enforced.
|
||||
describe('moderationBlockDetails', () => {
|
||||
// All sixteen keys the 2025 client's decoder names, every one at its "none" value.
|
||||
const NOT_BLOCKED = {
|
||||
ReportCategory: -1,
|
||||
Duration: 0,
|
||||
GameSessionId: 0,
|
||||
IsHostKick: false,
|
||||
Message: null,
|
||||
PlayerIdReporter: null,
|
||||
IsBan: false,
|
||||
IsVoiceModAutoban: false,
|
||||
IsDeviceBan: false,
|
||||
IsWarning: false,
|
||||
VoteKickReason: null,
|
||||
TimeoutStartedAt: null,
|
||||
AssociatedAccountUsername: null,
|
||||
ShowCreatorCodeOfConduct: false,
|
||||
TopMessageOverride: null,
|
||||
BottomMessageOverride: null,
|
||||
}
|
||||
const details = async (method: string, sub: string) => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`,
|
||||
{ method, headers: await bearer(sub) }
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
// The client POSTs this with no body, despite it being a pure read; the route
|
||||
// answers GET as well, and both methods serve the same body.
|
||||
test.each(['GET', 'POST'])(
|
||||
'%s reports "not blocked" for an unbanned player',
|
||||
async (method) => {
|
||||
// A report against them that nobody acted on is not a block.
|
||||
await submit({ PlayerIdReported: '220' }, await bearer())
|
||||
// ReportCategory -1 = Unknown (0 is a real category). Message is null, not the
|
||||
// reference stub's empty string — the client tells "no message" from a blank one.
|
||||
expect(await details(method, '220')).toEqual(NOT_BLOCKED)
|
||||
}
|
||||
)
|
||||
|
||||
test('401s without a bearer token', async () => {
|
||||
const res = await exports.default.fetch(
|
||||
`${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`
|
||||
)
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
// Duration and TimeoutStartedAt are a pair in the client — the block runs from the
|
||||
// start for the duration. The start is the report's created_at (nothing records when
|
||||
// the ban itself landed), and a permanent ban runs for the largest span the int
|
||||
// holds.
|
||||
test('describes a permanent ban', async () => {
|
||||
await submit(
|
||||
{ PlayerIdReported: '221', ReportCategory: '102', Details: 'slurs' },
|
||||
await bearer()
|
||||
)
|
||||
const [row] = await getReportsAgainst(env.DB, 221)
|
||||
await banFromReport(env.DB, row!.id)
|
||||
|
||||
expect(await details('POST', '221')).toEqual({
|
||||
...NOT_BLOCKED,
|
||||
ReportCategory: 102,
|
||||
Duration: 2_147_483_647,
|
||||
IsBan: true,
|
||||
// A fixed message — the report's `details` are the reporter's words, and
|
||||
// the reporter is not shown to the player they reported (PlayerIdReporter
|
||||
// stays null).
|
||||
Message: 'Rule violation',
|
||||
TimeoutStartedAt: row!.created_at,
|
||||
})
|
||||
})
|
||||
|
||||
// A timed ban's Duration is the seconds from the start to the expiry, so the pair
|
||||
// sums to `ban_expires` — not the seconds left as of the request.
|
||||
test('describes a timed ban as its report’s created_at plus the span to expiry', async () => {
|
||||
await submit({ PlayerIdReported: '222', ReportCategory: '103' }, await bearer())
|
||||
const [row] = await getReportsAgainst(env.DB, 222)
|
||||
const banExpires = new Date(Date.parse(row!.created_at) + 3600 * 1000)
|
||||
await banFromReport(env.DB, row!.id, { banExpires: banExpires.toISOString() })
|
||||
|
||||
expect(await details('GET', '222')).toEqual({
|
||||
...NOT_BLOCKED,
|
||||
ReportCategory: 103,
|
||||
Duration: 3600,
|
||||
IsBan: true,
|
||||
Message: 'Rule violation',
|
||||
TimeoutStartedAt: row!.created_at,
|
||||
})
|
||||
})
|
||||
|
||||
// A ban that has served its time is not a block, even though the row still says
|
||||
// `banned = 1` — the same rule `getActiveBan` applies for matchmake and login.
|
||||
test('reports "not blocked" once a ban has expired', async () => {
|
||||
await submit({ PlayerIdReported: '223' }, await bearer())
|
||||
const [row] = await getReportsAgainst(env.DB, 223)
|
||||
await banFromReport(env.DB, row!.id, { banExpires: '2020-01-01T00:00:00.000Z' })
|
||||
|
||||
expect(await details('GET', '223')).toEqual(NOT_BLOCKED)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('player warnings', () => {
|
||||
@@ -6300,9 +7466,11 @@ describe('openapi', () => {
|
||||
'POST /api/PlayerCheer/v1/create',
|
||||
'POST /api/PlayerReporting/v1/deviceId',
|
||||
'POST /api/PlayerReporting/v1/hile',
|
||||
'POST /api/PlayerReporting/v1/instantKick',
|
||||
'POST /api/PlayerReporting/v1/moderationBlockDetails',
|
||||
'POST /api/PlayerReporting/v1/referee',
|
||||
'POST /api/PlayerReporting/v3/create',
|
||||
'POST /api/PlayerReporting/v3/voteToKick',
|
||||
'POST /api/avatar/v1/lockeditems/bulk',
|
||||
'POST /api/avatar/v2/gifts/generate',
|
||||
'POST /api/customAvatarItems/GetCustomAvatarItemCurrentSavesForLegacyAvatarItems',
|
||||
@@ -6317,7 +7485,10 @@ describe('openapi', () => {
|
||||
'POST /api/inventions/v1/settags',
|
||||
'POST /api/inventions/v1/update',
|
||||
'POST /api/inventions/v1/updateprice',
|
||||
'POST /api/inventions/v2/delete',
|
||||
'POST /api/inventions/v4/publish',
|
||||
'POST /api/inventions/v6/save',
|
||||
'POST /api/inventions/v9/save',
|
||||
'POST /api/messages/v1/friendOnlineStatus',
|
||||
'POST /api/messages/v1/sendMultiple',
|
||||
'POST /api/messages/v2/send',
|
||||
@@ -6350,6 +7521,7 @@ describe('openapi', () => {
|
||||
'POST /outfits/bulk',
|
||||
'POST /statsigUserProperties',
|
||||
'PUT /api/customAvatarItems/v1/{id}',
|
||||
'PUT /api/inventions/v2/metadata',
|
||||
'PUT /api/playerevents/v2/{eventId}/accessibility',
|
||||
'PUT /api/playerevents/v2/{eventId}/description',
|
||||
'PUT /api/playerevents/v2/{eventId}/name',
|
||||
|
||||
@@ -925,7 +925,7 @@
|
||||
},
|
||||
{
|
||||
"Key": "Statsig.Enabled",
|
||||
"Value": "true",
|
||||
"Value": "false",
|
||||
"ActiveExperiments": null,
|
||||
"StartTime": null,
|
||||
"EndTime": null
|
||||
|
||||
@@ -26,10 +26,12 @@ export default defineConfig({
|
||||
// GET /all for the whole list (friend-graph changes notify both players),
|
||||
// DELETE to reset it between assertions.
|
||||
//
|
||||
// notifyPlayersEphemeral lands in the same list, tagged `ephemeral` and
|
||||
// carrying `playerIds` rather than `playerId` — the two sends differ in
|
||||
// whether an offline recipient gets the frame later, which is a thing worth
|
||||
// asserting (a cheer's effect is broadcast to a room this way).
|
||||
// The ephemeral sends land in the same list, tagged `ephemeral`:
|
||||
// notifyPlayerEphemeral carries `playerId` like the durable send,
|
||||
// notifyPlayersEphemeral carries `playerIds` for the whole batch. Durable
|
||||
// and ephemeral differ in whether an offline recipient gets the frame
|
||||
// later, which is a thing worth asserting (a cheer's effect is broadcast to
|
||||
// a room this way, a vote-to-kick is put to each player in it).
|
||||
script: `
|
||||
import { DurableObject } from 'cloudflare:workers'
|
||||
export class NotificationsHub extends DurableObject {
|
||||
@@ -38,6 +40,10 @@ export default defineConfig({
|
||||
this.sent.push({ playerId, notificationType, data })
|
||||
return { delivered: 0, queued: true }
|
||||
}
|
||||
async notifyPlayerEphemeral(playerId, notificationType, data) {
|
||||
this.sent.push({ playerId, ephemeral: true, notificationType, data })
|
||||
return { delivered: 0 }
|
||||
}
|
||||
async notifyPlayersEphemeral(playerIds, notificationType, data) {
|
||||
this.sent.push({ playerIds, ephemeral: true, notificationType, data })
|
||||
return { delivered: 0 }
|
||||
|
||||
+62
-48
@@ -71,19 +71,16 @@ const TOKEN_SCOPE =
|
||||
'offline_access profile rn rn.accounts rn.accounts.gc rn.api rn.chat rn.clubs rn.commerce rn.match.read rn.match.write rn.notify rn.rooms rn.storage'
|
||||
|
||||
/**
|
||||
* The `error_description` a banned account's grant is refused with. A fixed sentence,
|
||||
* never interpolated with the expiry, because `www`'s shared auth-messages table keys on
|
||||
* this exact string to put a real sentence in front of a player — anything varying would
|
||||
* fall through to the generic "you could not be signed in". Keep the two in sync.
|
||||
*/
|
||||
const BANNED_DESCRIPTION = 'this account is banned'
|
||||
|
||||
/**
|
||||
* The refusal when it is not THIS account that is banned but one it shares an identity
|
||||
* with (see bans-db's linked arms). Deliberately a different, vaguer sentence: the
|
||||
* account being refused may be an innocent housemate of a banned player, so telling them
|
||||
* "this account is banned" would be a lie, and naming the account we matched them to
|
||||
* would hand out somebody else's moderation record.
|
||||
* The `error_description` a grant is refused with when the caller's account is not itself
|
||||
* banned but shares an identity with one that is (see bans-db's linked arms). A fixed
|
||||
* sentence, because `www`'s shared auth-messages table keys on this exact string to put a
|
||||
* real sentence in front of a player — anything varying would fall through to the generic
|
||||
* "you could not be signed in". Keep the two in sync. Deliberately vague: the account
|
||||
* being refused may be an innocent housemate of a banned player, so telling them "this
|
||||
* account is banned" would be a lie, and naming the account we matched them to would hand
|
||||
* out somebody else's moderation record.
|
||||
*
|
||||
* A DIRECTLY banned account is not refused here at all — see the token grant.
|
||||
*/
|
||||
const BLOCKED_DESCRIPTION = 'this device or network is blocked'
|
||||
|
||||
@@ -608,19 +605,22 @@ const app = new Hono<App>()
|
||||
'the `rn.privilege` CLAIM (`BanVChat`, `BanRmChat`) — scope-shaped name, but the',
|
||||
'client reads it as a claim beside `role`, and it is absent for everyone else.',
|
||||
'',
|
||||
'**Bans.** Once the grant has resolved an account, a BANNED account is refused a',
|
||||
'token at all (`invalid_grant`) — every grant, including a refresh. A ban is a',
|
||||
'`report` row with `banned` set (the `api` worker owns that table); it lifts on its',
|
||||
'own when `ban_expires` passes, and never if that is null.',
|
||||
'**Bans.** A BANNED account still gets a token — every grant, including a refresh.',
|
||||
'A ban is a `report` row with `banned` set (the `api` worker owns that table); it',
|
||||
'lifts on its own when `ban_expires` passes, and never if that is null. The token',
|
||||
'is what lets the client reach `api`’s `/api/PlayerReporting/v1/moderationBlockDetails`',
|
||||
'and show the player the block screen that explains the ban; the ban itself is',
|
||||
'enforced by `match`, which refuses every matchmake for a banned player, so a token',
|
||||
'gets them as far as that screen and no further.',
|
||||
'',
|
||||
'The refusal follows the player, not just the account: it also catches an account',
|
||||
'that shares a PROVEN platform identity (a `platform_account` link) or an IP',
|
||||
'(`signupIp`/`lastLoginIp`, or the address this request came from) with a banned',
|
||||
'one, and a `create_account` carrying either is refused BEFORE it mints anything.',
|
||||
'Those two arms are the operator’s `BAN_EVASION_MATCH` knob (`ip`, `platform`, or',
|
||||
'`off`); the ban on the account itself is always enforced. A linked match answers a',
|
||||
'deliberately vaguer description than a direct one — the account refused may belong',
|
||||
'to a housemate of the banned player rather than to them.',
|
||||
'What IS refused here (`invalid_grant`) is ban EVASION: an account that shares a',
|
||||
'PROVEN platform identity (a `platform_account` link) or an IP (`signupIp`/',
|
||||
'`lastLoginIp`, or the address this request came from) with a banned one, and a',
|
||||
'`create_account` carrying either, which is refused BEFORE it mints anything. Such',
|
||||
'an account has no ban of its own for the block screen to describe, so there is',
|
||||
'nothing to let it in for. Those two arms are the operator’s `BAN_EVASION_MATCH`',
|
||||
'knob (`ip`, `platform`, or `off`). The description is deliberately vague — the',
|
||||
'account refused may belong to a housemate of the banned player rather than to them.',
|
||||
].join('\n'),
|
||||
requestBody: form(
|
||||
TokenRequest,
|
||||
@@ -633,7 +633,7 @@ const app = new Hono<App>()
|
||||
[
|
||||
'Unusable grant: bad credentials, an unverifiable platform or platform_auth, an',
|
||||
'invalid/expired refresh token, a missing account identifier, a signup cap reached,',
|
||||
'or a banned account',
|
||||
'or an account sharing a banned one’s device or network',
|
||||
].join(' ')
|
||||
),
|
||||
500: json(
|
||||
@@ -789,9 +789,9 @@ const app = new Hono<App>()
|
||||
// via create_account or /account/me/changepassword.
|
||||
let accountId: string
|
||||
if (grantType === 'create_account') {
|
||||
// A banned player's next move is a new account, so the ban is checked BEFORE
|
||||
// one is minted — against the only identity a signup has, the IP it came from
|
||||
// and the platform identity it just proved. Refusing after the fact (as the
|
||||
// A banned player's next move is a new account, so the evasion arms are checked
|
||||
// BEFORE one is minted — against the only identity a signup has, the IP it came
|
||||
// from and the platform identity it just proved. Refusing after the fact (as the
|
||||
// shared check below would) still refuses the token, but leaves the account
|
||||
// row behind and burns a slot off both signup caps, so the evader gets to keep
|
||||
// making them.
|
||||
@@ -987,14 +987,19 @@ const app = new Hono<App>()
|
||||
await setLoginContext(c.env.DB, resolvedId, { deviceId, deviceClass, ip: clientIp })
|
||||
}
|
||||
|
||||
// A banned player gets no token — and with no token every other worker is shut to
|
||||
// them, so this is the outer wall of a ban; matchmaking's refusal is the inner
|
||||
// one, which still has to exist because a token issued before the ban stays valid
|
||||
// until it expires.
|
||||
// A DIRECTLY banned account still gets its token. The client needs one to reach
|
||||
// `api`'s moderationBlockDetails, which is where the player is TOLD they are banned
|
||||
// (category, time left, "Rule violation") — refused here, they would only ever see
|
||||
// a failed sign-in. The ban is enforced by matchmaking instead, which refuses every
|
||||
// matchmake for a banned player, so the token gets them as far as the block screen
|
||||
// and no further. Logged, so the operator can see a banned player signing in.
|
||||
//
|
||||
// Checked once here, after the grant has resolved an account, so it covers every
|
||||
// grant: password, cached_login and a refresh_token redeemed by a client that has
|
||||
// been running since before the ban. Deliberately AFTER the credential checks —
|
||||
// Ban EVASION is still refused here: an account that merely shares a device or
|
||||
// network with a banned one has no ban of its own for that screen to describe, so
|
||||
// there is nothing to let it in for — and letting it in is exactly what the evader
|
||||
// wants. Checked once here, after the grant has resolved an account, so it covers
|
||||
// every grant: password, cached_login and a refresh_token redeemed by a client that
|
||||
// has been running since before the ban. Deliberately AFTER the credential checks —
|
||||
// a wrong password is still "invalid account_id or password", so this can't be
|
||||
// used to probe whether an account exists or is banned without knowing it.
|
||||
//
|
||||
@@ -1007,8 +1012,8 @@ const app = new Hono<App>()
|
||||
identity: { ip: clientIp, platform: verifiedPlatform, platformId: verifiedPlatformId },
|
||||
arms: banEvasionMatch(c.env.BAN_EVASION_MATCH),
|
||||
})
|
||||
if (ban) {
|
||||
logger.info('token refused: player banned', {
|
||||
if (ban && ban.via !== 'account') {
|
||||
logger.info('token refused: ban evasion', {
|
||||
accountId,
|
||||
grantType,
|
||||
via: ban.via,
|
||||
@@ -1016,13 +1021,15 @@ const app = new Hono<App>()
|
||||
reportId: ban.ban.id,
|
||||
banExpires: ban.ban.ban_expires,
|
||||
})
|
||||
return c.json(
|
||||
{
|
||||
error: 'invalid_grant',
|
||||
error_description: ban.via === 'account' ? BANNED_DESCRIPTION : BLOCKED_DESCRIPTION,
|
||||
},
|
||||
400
|
||||
)
|
||||
return c.json({ error: 'invalid_grant', error_description: BLOCKED_DESCRIPTION }, 400)
|
||||
}
|
||||
if (ban) {
|
||||
logger.info('token issued to banned account', {
|
||||
accountId,
|
||||
grantType,
|
||||
reportId: ban.ban.id,
|
||||
banExpires: ban.ban.ban_expires,
|
||||
})
|
||||
}
|
||||
|
||||
// Never sign with an empty key. An empty JWT_SECRET (misconfigured/missing
|
||||
@@ -1040,8 +1047,9 @@ const app = new Hono<App>()
|
||||
|
||||
// Stamp the account's elevated roles into the token's `role` claim so the client
|
||||
// authorizes developer/moderator powers from the token itself (not just the
|
||||
// /role/* lookups). One read of the just-resolved account; roles thus refresh on
|
||||
// every login and every refresh_token grant.
|
||||
// /role/* lookups), and its Plus flag into `rn.plus`. One read of the just-resolved
|
||||
// account serves both; they thus refresh on every login and every refresh_token
|
||||
// grant.
|
||||
const roleAccount = await getAccount(c.env.DB, Number(accountId))
|
||||
// A refresh grant posts no platform of its own, so the identity comes off the
|
||||
// account — the same read, and the only place the bound identity is authoritative.
|
||||
@@ -1056,7 +1064,13 @@ const app = new Hono<App>()
|
||||
jwtSecret,
|
||||
accountRoles(roleAccount),
|
||||
accountPrivileges(roleAccount),
|
||||
version
|
||||
version,
|
||||
// Rec Room Plus, off the same account read as the roles above — `econ` decides the
|
||||
// CampusCard and the subscriber discount from this claim alone, so it never has to
|
||||
// load the account. It therefore refreshes on every login and every refresh_token
|
||||
// grant, and only then: a player who claims Plus on the website keeps a token that
|
||||
// says otherwise until they sign in again.
|
||||
roleAccount?.hasPlus === true
|
||||
)
|
||||
// Issue a fresh, persisted refresh token (single-use; the client redeems it via
|
||||
// grant_type=refresh_token). A refresh grant thus rotates its token.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { resolver } from 'hono-openapi'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { PlatformType } from '@repo/domain/src/enums'
|
||||
|
||||
import type { OpenAPIV3_1 } from 'openapi-types'
|
||||
|
||||
/**
|
||||
@@ -49,24 +51,13 @@ export function form(schema: z.ZodType, description: string): OpenAPIV3_1.Reques
|
||||
}
|
||||
|
||||
/**
|
||||
* PlatformType, the client's platform enum. Declaration order is wire order, and is
|
||||
* the single source for the schema and description below. The `platform` form field
|
||||
* is posted as the integer; the token's `platform` claim carries the name.
|
||||
* PlatformType, the client's platform enum — the single source for the schema and
|
||||
* description below. It lives in `@repo/domain` rather than here because the link table
|
||||
* (`platform-db`) and the website's benefits claim both need the values, and neither has
|
||||
* any business importing this module's zod/hono-openapi dependencies. Re-exported so
|
||||
* `import { PlatformType } from './openapi'` keeps working alongside the schemas.
|
||||
*/
|
||||
export const PlatformType = {
|
||||
All: -1,
|
||||
Steam: 0,
|
||||
Oculus: 1,
|
||||
PlayStation: 2,
|
||||
Xbox: 3,
|
||||
RecNet: 4,
|
||||
IOS: 5,
|
||||
GooglePlay: 6,
|
||||
Standalone: 7,
|
||||
Pico: 8,
|
||||
} as const
|
||||
|
||||
export type PlatformType = (typeof PlatformType)[keyof typeof PlatformType]
|
||||
export { PlatformType } from '@repo/domain/src/enums'
|
||||
|
||||
/**
|
||||
* A PlatformType by value. Only Steam and Oculus (Meta) can actually be verified —
|
||||
|
||||
@@ -19,8 +19,18 @@
|
||||
* A link is only ever written from a VERIFIED identity (a Steam-signed ticket or a
|
||||
* Meta-validated nonce). It is what turns "this platform user" into "may enter this
|
||||
* account with no password", so an unproven `platform_id` must never reach it.
|
||||
*
|
||||
* Not every link is a LOGIN, though. The table is the account's set of external
|
||||
* identities, and some are stored for what they entitle the player to rather than for
|
||||
* entry: `PlatformType.Discord` (101) is written by the website's benefits claim, from an
|
||||
* OAuth2 code exchange Discord itself vouched for. Nobody signs in with it —
|
||||
* `verifyPlatformProof` answers `unsupported` for anything but Steam and Meta, so a
|
||||
* `cached_login` naming platform 101 is refused — and the picker must not offer it
|
||||
* either. See {@link CACHED_LOGIN_PLATFORMS}, which is what keeps those two in step.
|
||||
*/
|
||||
|
||||
import { PlatformType } from '@repo/domain/src/enums'
|
||||
|
||||
/** Schema DDL (mirror of migrations/0007_platform_accounts.sql, sans the backfill). */
|
||||
export const PLATFORM_SCHEMA_DDL: string[] = [
|
||||
`CREATE TABLE IF NOT EXISTS platform_account (
|
||||
@@ -64,6 +74,38 @@ export const PLATFORM_BACKFILL_SQL = `INSERT OR IGNORE INTO platform_account (ac
|
||||
WHERE json_extract(data, '$.platformId') IS NOT NULL
|
||||
AND json_extract(data, '$.platformId') <> ''`
|
||||
|
||||
/**
|
||||
* The platforms a cached login can actually be redeemed for — the ones
|
||||
* `verifyPlatformProof` can prove, which is Steam (a Steam-signed ticket) and Meta/Oculus
|
||||
* (a Meta-validated nonce).
|
||||
*
|
||||
* This is the picker's filter, and it exists to keep a promise the picker's own API
|
||||
* documentation makes: "an entry here is always redeemable by a `cached_login` grant
|
||||
* (both read the same table)". Once the table began holding identities that are NOT
|
||||
* credentials — Discord, from the website's benefits claim — listing every row would have
|
||||
* broken that promise in two ways at once. The client would be offered an account it can
|
||||
* never log into (the grant refuses platform 101 outright), and, worse, the picker is
|
||||
* PUBLIC and unauthenticated: `GET /cachedlogin/forplatformid/101/<snowflake>` would have
|
||||
* told anyone which RecFlare account a given Discord user owns, and the bulk route would
|
||||
* have done it for a list of them at once. A Discord id is trivially readable by anyone in
|
||||
* a shared server, so that is a deanonymisation of every player who claimed benefits.
|
||||
*
|
||||
* Adding a platform here means asserting `verifyPlatformProof` can prove it. Filtering
|
||||
* happens in the two picker reads only — {@link isPlatformIdentityLinked},
|
||||
* {@link countAccountsForPlatformIdentity} and {@link getLinksForAccount} deliberately see
|
||||
* every link, because they answer "is this identity taken / whose is it", which is exactly
|
||||
* the question the benefits claim's once-only guard asks about a Discord id.
|
||||
*/
|
||||
export const CACHED_LOGIN_PLATFORMS: readonly number[] = [PlatformType.Steam, PlatformType.Oculus]
|
||||
|
||||
/**
|
||||
* `IN (…)` fragment for the allowlist, so the filter is applied by the query rather than
|
||||
* in JS. Placeholders are numbered from ?2 because the one caller binds the platform id
|
||||
* as ?1 — explicit indices rather than bare `?`, which SQLite would number by position
|
||||
* and quietly renumber the moment another parameter is added ahead of it.
|
||||
*/
|
||||
const CACHED_LOGIN_FILTER = `platform IN (${CACHED_LOGIN_PLATFORMS.map((_, i) => `?${i + 2}`).join(', ')})`
|
||||
|
||||
/** One account ↔ platform identity link. */
|
||||
export interface PlatformLink {
|
||||
accountId: number
|
||||
@@ -119,6 +161,9 @@ export async function getLinksForPlatformIdentity(
|
||||
platformId: string
|
||||
): Promise<PlatformLink[]> {
|
||||
if (platformId === '') return []
|
||||
// Asking about a platform nobody can log in from yields nothing at all, rather than a
|
||||
// list the grant would refuse — see CACHED_LOGIN_PLATFORMS.
|
||||
if (!CACHED_LOGIN_PLATFORMS.includes(platform)) return []
|
||||
const { results } = await db
|
||||
.prepare(
|
||||
`${SELECT_LINK} WHERE platform = ?1 AND platform_id = ?2 ORDER BY linked_at, account_id`
|
||||
@@ -138,9 +183,15 @@ export async function getLinksForPlatformId(
|
||||
platformId: string
|
||||
): Promise<PlatformLink[]> {
|
||||
if (platformId === '') return []
|
||||
// Matches on any platform a cached login can be redeemed for — but only those. This is
|
||||
// the route a bare id takes, so without the filter a Discord snowflake posted here
|
||||
// would resolve its account even though naming platform 101 explicitly would not.
|
||||
const { results } = await db
|
||||
.prepare(`${SELECT_LINK} WHERE platform_id = ?1 ORDER BY linked_at, account_id`)
|
||||
.bind(platformId)
|
||||
.prepare(
|
||||
`${SELECT_LINK} WHERE platform_id = ?1 AND ${CACHED_LOGIN_FILTER}
|
||||
ORDER BY linked_at, account_id`
|
||||
)
|
||||
.bind(platformId, ...CACHED_LOGIN_PLATFORMS)
|
||||
.all<LinkRow>()
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ import {
|
||||
createReport,
|
||||
SCHEMA_DDL as REPORTS_SCHEMA_DDL,
|
||||
} from '../../../../api/src/reports-db'
|
||||
import { PlatformType } from '../../openapi'
|
||||
import {
|
||||
countAccountsForPlatformIdentity,
|
||||
getLinksForAccount,
|
||||
linkPlatformIdentity,
|
||||
PLATFORM_BACKFILL_SQL,
|
||||
@@ -87,8 +89,8 @@ beforeAll(async () => {
|
||||
IsDorm: false,
|
||||
SubRooms: [{ SubRoomId: 23, UnitySceneId: ORIENTATION_SCENE, MaxPlayers: 1 }],
|
||||
})
|
||||
// Report table (owned by the api worker) — a banned account is refused a token, and
|
||||
// a ban is a report row with `banned` set.
|
||||
// Report table (owned by the api worker) — a ban is a report row with `banned` set;
|
||||
// the token grant reads it for the evasion arms.
|
||||
for (const stmt of REPORTS_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
})
|
||||
|
||||
@@ -421,6 +423,52 @@ describe('auth worker routes', () => {
|
||||
])
|
||||
})
|
||||
|
||||
// A Discord link is an external identity, not a credential — `www`'s benefits claim
|
||||
// writes one so a claimed Discord user can't claim again on a second account. It must
|
||||
// stay invisible to BOTH picker routes, for two independent reasons:
|
||||
//
|
||||
// - Every entry the picker lists is promised to be redeemable by a `cached_login`
|
||||
// grant, and that grant refuses platform 101 outright (verifyPlatformProof answers
|
||||
// `unsupported`). Listing one offers the client an account it can never log into.
|
||||
// - These routes are PUBLIC and unauthenticated, and a Discord snowflake is readable by
|
||||
// anyone sharing a server with its owner. Answering here would turn the login picker
|
||||
// into a lookup from "Discord user" to "their RecFlare account", for every player who
|
||||
// ever claimed benefits.
|
||||
//
|
||||
// The bare-id route is checked too, and it is the easier one to miss: it matches on ANY
|
||||
// platform, so it would resolve the snowflake even though naming 101 explicitly did not.
|
||||
test('never lists a Discord link in either cached-login picker', async () => {
|
||||
const discordId = '308994132968210433'
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(JSON.stringify({ accountId: 31399, username: 'DiscordClaimer', hasPlus: true }))
|
||||
.run()
|
||||
await linkPlatformIdentity(env.DB, 31399, PlatformType.Discord, discordId)
|
||||
|
||||
// Named explicitly…
|
||||
const named = await exports.default.fetch(
|
||||
`${ORIGIN}/cachedlogin/forplatformid/${PlatformType.Discord}/${discordId}`
|
||||
)
|
||||
expect(named.status).toBe(200)
|
||||
expect(await named.json()).toEqual([])
|
||||
|
||||
// …and via the bare id, which matches across platforms.
|
||||
const bare = await exports.default.fetch(`${ORIGIN}/cachedlogin/forplatformid/any/${discordId}`)
|
||||
expect(await bare.json()).toEqual([])
|
||||
|
||||
// …and through the bulk friends-resolution route, which takes bare ids only.
|
||||
const bulk = await exports.default.fetch(`${ORIGIN}/cachedlogin/forplatformids`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ id: discordId }).toString(),
|
||||
})
|
||||
expect(await bulk.json()).toEqual([])
|
||||
|
||||
// The link is still really there — this is a filtered READ, not a failed write.
|
||||
await expect(
|
||||
countAccountsForPlatformIdentity(env.DB, PlatformType.Discord, discordId)
|
||||
).resolves.toBe(1)
|
||||
})
|
||||
|
||||
// The 20250424.01 build POSTs the picker lookup with a platform-attestation form body
|
||||
// instead of GETting it. Nothing reads that body yet, so both methods must answer the
|
||||
// same list — otherwise the newer client's login screen comes up empty.
|
||||
@@ -577,6 +625,9 @@ describe('auth worker routes', () => {
|
||||
expect(payload.role).not.toContain('junior')
|
||||
// No privileges to carry, so the claim is absent rather than an empty array.
|
||||
expect(payload['rn.privilege']).toBeUndefined()
|
||||
// Same for Plus: omitted rather than `false`, so a non-subscriber's token is
|
||||
// byte-for-byte what it was before `rn.plus` existed.
|
||||
expect(payload['rn.plus']).toBeUndefined()
|
||||
expect(payload.scope).toContain('rn.api')
|
||||
})
|
||||
|
||||
@@ -615,6 +666,64 @@ describe('auth worker routes', () => {
|
||||
expect(payload.role).toEqual(expect.arrayContaining(['gameClient', 'developer', 'moderator']))
|
||||
})
|
||||
|
||||
// Rec Room Plus rides on the token as `rn.plus`, stamped from `account.hasPlus` — which
|
||||
// the website's Discord benefits claim sets. `econ` decides the CampusCard and the
|
||||
// subscriber discount from this claim ALONE and never reads the account, so if this
|
||||
// stops being stamped, Plus silently stops existing for everyone.
|
||||
//
|
||||
// It is a CLAIM, not a scope: `scope` is a fixed list the client parses, and this is
|
||||
// ours. And it is not a role — the `developer` role does not confer Plus.
|
||||
test('POST /connect/token stamps rn.plus for a hasPlus account', async () => {
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(
|
||||
JSON.stringify({
|
||||
accountId: 93,
|
||||
username: 'PlusPlayer',
|
||||
passwordHash: await hashPassword(LOGIN_PASSWORD),
|
||||
hasPlus: true,
|
||||
})
|
||||
)
|
||||
.run()
|
||||
const payload = await tokenFor(`account_id=93&password=${LOGIN_PASSWORD}`)
|
||||
expect(payload['rn.plus']).toBe(true)
|
||||
expect(payload.scope).not.toContain('rn.plus')
|
||||
// Plus is not an elevated role, and does not come with one.
|
||||
expect(payload.role).not.toContain('developer')
|
||||
})
|
||||
|
||||
// The flag is read at LOGIN, so signing in again is what activates it — the website's
|
||||
// claim page and `runx admin grant-plus` both say so, and this is the mechanism behind it.
|
||||
//
|
||||
// This also pins that `hasPlus` stands ALONE: the flag is set here by raw SQL, exactly as
|
||||
// `runx admin grant-plus` sets it, with no Discord app configured, no OAuth exchange and
|
||||
// no `platform_account` link anywhere. An operator must be able to grant Plus outright.
|
||||
test('rn.plus refreshes on the next login after hasPlus is set, with no Discord link', async () => {
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(
|
||||
JSON.stringify({
|
||||
accountId: 94,
|
||||
username: 'LateClaimer',
|
||||
passwordHash: await hashPassword(LOGIN_PASSWORD),
|
||||
})
|
||||
)
|
||||
.run()
|
||||
// Before claiming: no Plus.
|
||||
expect((await tokenFor(`account_id=94&password=${LOGIN_PASSWORD}`))['rn.plus']).toBeUndefined()
|
||||
|
||||
// The website's claim writes the flag…
|
||||
await env.DB.prepare(
|
||||
"UPDATE account SET data = json_set(data, '$.hasPlus', json('true')) WHERE account_id = 94"
|
||||
).run()
|
||||
|
||||
// …and the NEXT token carries it. The one already in the player's hands does not,
|
||||
// which is exactly why they have to sign in again.
|
||||
expect((await tokenFor(`account_id=94&password=${LOGIN_PASSWORD}`))['rn.plus']).toBe(true)
|
||||
|
||||
// Nothing linked a Discord identity to this account, and Plus does not care.
|
||||
const links = await getLinksForAccount(env.DB, 94)
|
||||
expect(links.filter((l) => l.platform === PlatformType.Discord)).toEqual([])
|
||||
})
|
||||
|
||||
test('POST /connect/token stamps the junior role for an isJunior account', async () => {
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(
|
||||
@@ -1327,38 +1436,35 @@ describe('CORS', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// A banned account is refused a token at all — the outer wall of a ban, since with no
|
||||
// token every other worker is shut to it. The ban is a `report` row with `banned` set
|
||||
// (the api worker owns that table); matchmaking enforces the same ban on tokens issued
|
||||
// before it was handed down.
|
||||
// A banned account is still issued a token: the game client needs one to reach the api
|
||||
// worker's moderationBlockDetails, which is where the player is shown WHY they are
|
||||
// blocked. The ban is a `report` row with `banned` set (the api worker owns that table)
|
||||
// and is enforced by matchmaking, which refuses every matchmake for a banned player — so
|
||||
// the token gets them to the block screen and no further.
|
||||
describe('banned accounts', () => {
|
||||
test('POST /connect/token refuses a password grant from a banned account', async () => {
|
||||
test('POST /connect/token issues a token to a banned account', async () => {
|
||||
await seedAccount(6101, 'BannedPlayer')
|
||||
await banAccount(6101)
|
||||
|
||||
const res = await postToken(`account_id=6101&password=${LOGIN_PASSWORD}`)
|
||||
expect(res.status).toBe(400)
|
||||
expect(res.json.error).toBe('invalid_grant')
|
||||
// The exact sentence www's shared auth-messages table keys on to put a real
|
||||
// message in front of the player — changing it silently downgrades that to the
|
||||
// generic "you could not be signed in".
|
||||
expect(res.json.error_description).toBe('this account is banned')
|
||||
expect(res.status).toBe(200)
|
||||
expect(decodePayload(res.json.access_token as string).sub).toBe('6101')
|
||||
})
|
||||
|
||||
test('POST /connect/token refuses a username login from a banned account', async () => {
|
||||
test('POST /connect/token issues a token to a banned account logging in by username', async () => {
|
||||
await seedAccount(6102, 'BannedByName')
|
||||
await banAccount(6102)
|
||||
|
||||
const res = await postToken(
|
||||
`grant_type=password&username=BannedByName&password=${LOGIN_PASSWORD}`
|
||||
)
|
||||
expect(res.status).toBe(400)
|
||||
expect(res.json.error_description).toBe('this account is banned')
|
||||
expect(res.status).toBe(200)
|
||||
expect(decodePayload(res.json.access_token as string).sub).toBe('6102')
|
||||
})
|
||||
|
||||
// A client that was already signed in when the ban landed still holds a valid refresh
|
||||
// token; redeeming it must not renew the session.
|
||||
test('POST /connect/token refuses to refresh a banned account’s session', async () => {
|
||||
// A client that was already signed in when the ban landed refreshes as normal — its
|
||||
// next matchmake is what refuses it, and moderationBlockDetails says why.
|
||||
test('POST /connect/token refreshes a banned account’s session', async () => {
|
||||
await seedAccount(6103, 'BannedLater')
|
||||
const login = await postToken(`account_id=6103&password=${LOGIN_PASSWORD}`)
|
||||
expect(login.status).toBe(200)
|
||||
@@ -1368,13 +1474,12 @@ describe('banned accounts', () => {
|
||||
const refreshed = await postToken(
|
||||
`grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`
|
||||
)
|
||||
expect(refreshed.status).toBe(400)
|
||||
expect(refreshed.json.error_description).toBe('this account is banned')
|
||||
expect(refreshed.status).toBe(200)
|
||||
expect(decodePayload(refreshed.json.access_token as string).sub).toBe('6103')
|
||||
})
|
||||
|
||||
// The ban check runs AFTER the credential check, so a wrong password on a banned
|
||||
// account still answers the ordinary bad-credential refusal — it can't be used to
|
||||
// find out whether an account exists or is banned without knowing its password.
|
||||
// A ban does not loosen the credential check: a wrong password on a banned account is
|
||||
// the ordinary bad-credential refusal.
|
||||
test('a wrong password on a banned account is still a credential refusal', async () => {
|
||||
await seedAccount(6104, 'BannedWrongPw')
|
||||
await banAccount(6104)
|
||||
@@ -1384,23 +1489,13 @@ describe('banned accounts', () => {
|
||||
expect(res.json.error_description).toBe('invalid account_id or password')
|
||||
})
|
||||
|
||||
// A timed ban lifts itself when its expiry passes; nothing clears the flag.
|
||||
test('an expired ban lets the account sign in again', async () => {
|
||||
await seedAccount(6105, 'ServedTime')
|
||||
await banAccount(6105, '2020-01-01T00:00:00.000Z')
|
||||
|
||||
const res = await postToken(`account_id=6105&password=${LOGIN_PASSWORD}`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(decodePayload(res.json.access_token as string).sub).toBe('6105')
|
||||
})
|
||||
|
||||
test('a ban that has not expired yet still refuses the login', async () => {
|
||||
test('a ban that has not expired yet still issues a token', async () => {
|
||||
await seedAccount(6106, 'StillServing')
|
||||
await banAccount(6106, new Date(Date.now() + 3_600_000).toISOString())
|
||||
|
||||
const res = await postToken(`account_id=6106&password=${LOGIN_PASSWORD}`)
|
||||
expect(res.status).toBe(400)
|
||||
expect(res.json.error_description).toBe('this account is banned')
|
||||
expect(res.status).toBe(200)
|
||||
expect(decodePayload(res.json.access_token as string).sub).toBe('6106')
|
||||
})
|
||||
|
||||
// A report is not a ban until a moderator converts it.
|
||||
@@ -1425,8 +1520,10 @@ describe('banned accounts', () => {
|
||||
|
||||
// The ban follows the player past the account it was written on: a login from an account
|
||||
// that shares a proven platform identity or an IP with a banned one is refused, and a
|
||||
// signup carrying either is refused before it mints anything. See the api worker's
|
||||
// bans-db.ts for the arms and the BAN_EVASION_MATCH knob.
|
||||
// signup carrying either is refused before it mints anything. Unlike the banned account
|
||||
// itself, such an account has no ban of its own for the block screen to describe, so
|
||||
// there is nothing to let it in for. See the api worker's bans-db.ts for the arms and
|
||||
// the BAN_EVASION_MATCH knob.
|
||||
describe('ban evasion at the token endpoint', () => {
|
||||
/** Seed a loginable account carrying the IPs it signed up / last logged in from. */
|
||||
const account = async (id: number, name: string, ips: Record<string, string> = {}) => {
|
||||
@@ -1506,7 +1603,7 @@ describe('ban evasion at the token endpoint', () => {
|
||||
})
|
||||
|
||||
// The knob an operator reaches for when the IP arm locks out real players.
|
||||
test('BAN_EVASION_MATCH=platform drops the IP arm but keeps the direct ban', async () => {
|
||||
test('BAN_EVASION_MATCH=platform drops the IP arm but keeps the platform one', async () => {
|
||||
const original = env.BAN_EVASION_MATCH
|
||||
await account(6320, 'KnobBanned', { signupIp: '203.0.113.50' })
|
||||
await linkPlatformIdentity(env.DB, 6320, 0, 'steam-knobevader')
|
||||
@@ -1524,10 +1621,9 @@ describe('ban evasion at the token endpoint', () => {
|
||||
|
||||
env.BAN_EVASION_MATCH = 'off'
|
||||
expect((await login(6322)).status).toBe(200)
|
||||
// The banned account itself is refused whatever the knob says.
|
||||
const banned = await login(6320)
|
||||
expect(banned.status).toBe(400)
|
||||
expect(banned.json.error_description).toBe('this account is banned')
|
||||
// The banned account itself signs in whatever the knob says — its ban is
|
||||
// enforced at matchmake, and the knob only governs the linked arms.
|
||||
expect((await login(6320)).status).toBe(200)
|
||||
} finally {
|
||||
env.BAN_EVASION_MATCH = original
|
||||
}
|
||||
|
||||
@@ -14,13 +14,15 @@ const PAGE_SOURCES = [
|
||||
'StoreConsumables',
|
||||
]
|
||||
|
||||
interface Section {
|
||||
id: string
|
||||
sectionType: number
|
||||
sectionSubType: string
|
||||
source: string
|
||||
sourceMetadata: string | null
|
||||
displayMetadata: string | null
|
||||
/**
|
||||
* A section as published. The reference captures are camelCase and the hand-authored
|
||||
* store pages PascalCase; the client's decoder is case-insensitive, so both are served
|
||||
* as-is and the tests read either spelling.
|
||||
*/
|
||||
type Section = Record<string, unknown>
|
||||
|
||||
function field(section: Section, name: string): unknown {
|
||||
return section[name] ?? section[name[0].toUpperCase() + name.slice(1)]
|
||||
}
|
||||
|
||||
/** Fetch a page source and return its parsed body. */
|
||||
@@ -44,16 +46,17 @@ describe('GET /sections/pagesource/:type', () => {
|
||||
// is reachable without the worker knowing its name.
|
||||
it.each(PAGE_SOURCES)('serves %s', async (type) => {
|
||||
const sections = await pageSource(type)
|
||||
expect(sections.length).toBeGreaterThan(0)
|
||||
expect(Array.isArray(sections)).toBe(true)
|
||||
for (const section of sections) {
|
||||
expect(typeof section.id).toBe('string')
|
||||
expect(typeof section.sectionType).toBe('number')
|
||||
expect(typeof section.source).toBe('string')
|
||||
expect(typeof field(section, 'id')).toBe('string')
|
||||
expect(typeof field(section, 'sectionType')).toBe('number')
|
||||
expect(typeof field(section, 'source')).toBe('string')
|
||||
// An embedded JSON *string* the client parses itself, not an object — or null,
|
||||
// which several store and play-highlight sections use.
|
||||
if (section.displayMetadata !== null) {
|
||||
expect(typeof section.displayMetadata).toBe('string')
|
||||
expect(() => JSON.parse(section.displayMetadata as string)).not.toThrow()
|
||||
const display = field(section, 'displayMetadata')
|
||||
if (display !== null && display !== undefined) {
|
||||
expect(typeof display).toBe('string')
|
||||
expect(() => JSON.parse(display as string)).not.toThrow()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -81,35 +84,17 @@ describe('GET /sections/pagesource/:type', () => {
|
||||
// that this builder would drop — that is the reference's data, not a mistake to fix here.
|
||||
it('StoreCategories only carries sections the store page builder keeps', async () => {
|
||||
for (const section of await pageSource('StoreCategories')) {
|
||||
expect([4, 13]).toContain(section.sectionType)
|
||||
expect(section.displayMetadata).toBeTruthy()
|
||||
expect(() => JSON.parse(section.displayMetadata as string)).not.toThrow()
|
||||
if (section.sectionType === 13) {
|
||||
expect(['CuratedList', 'PageSource']).toContain(section.source)
|
||||
expect(section.sourceMetadata).toBeTruthy()
|
||||
expect([4, 13]).toContain(field(section, 'sectionType'))
|
||||
const display = field(section, 'displayMetadata')
|
||||
expect(display).toBeTruthy()
|
||||
expect(() => JSON.parse(display as string)).not.toThrow()
|
||||
if (field(section, 'sectionType') === 13) {
|
||||
expect(['CuratedList', 'PageSource']).toContain(field(section, 'source'))
|
||||
expect(field(section, 'sourceMetadata')).toBeTruthy()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('serves the StoreCategories page', async () => {
|
||||
const sections = await pageSource('StoreCategories')
|
||||
expect(sections[0]).toEqual({
|
||||
id: 'store-featured',
|
||||
// StoreItemsSection: a store CATEGORY is drawn as the product carousel.
|
||||
sectionType: 4,
|
||||
sectionSubType: 'StoreCategory_Featured',
|
||||
source: 'CuratedList',
|
||||
// The curated list the `lists` worker serves from /curatedlists/bulk.
|
||||
sourceMetadata: '17859340',
|
||||
displayMetadata: expect.stringContaining('"DisplayTitle":"Featured"'),
|
||||
})
|
||||
// displayMetadata must be non-empty and parse, or the builder drops the section.
|
||||
const display = JSON.parse(sections[0].displayMetadata as string) as {
|
||||
categoryUriNames: string
|
||||
}
|
||||
expect(display.categoryUriNames).toBe('featured,new')
|
||||
})
|
||||
|
||||
// The asset manifest is case-sensitive and there is no index to fold case against, so
|
||||
// the name has to match the file exactly.
|
||||
it('404s a name whose case does not match the file', async () => {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
|
||||
+391
-141
@@ -17,14 +17,14 @@ import {
|
||||
setOutfit,
|
||||
} from '@repo/domain'
|
||||
import { intVar, logger, withCleanSpec, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||
import { validateAndGetAccountId, validateAndGetRoles, validateAndGetVersion } from '@repo/jwt'
|
||||
import { validateAndGetAccountId, validateAndGetPlus, validateAndGetVersion } from '@repo/jwt'
|
||||
|
||||
import {
|
||||
getCustomAvatarItems,
|
||||
toUgcPurchasable,
|
||||
UGC_ITEM_TYPE_CUSTOM_AVATAR_ITEM,
|
||||
} from '../../api/src/custom-avatar-items-db'
|
||||
import { getInventionById, toSaveResult } from '../../api/src/inventions-db'
|
||||
import { getInventionById, toInventionV9, toSaveResult } from '../../api/src/inventions-db'
|
||||
// The profanity filter behind `api`'s `POST /api/sanitize/v1`, imported rather than copied
|
||||
// so a gift note is masked by the very same word list every other player-typed string is.
|
||||
import { censorSwears } from '../../api/src/sanitize'
|
||||
@@ -40,6 +40,7 @@ import defaultAvatarItems from '../static/default-avatar-items.json'
|
||||
import defaultAvatar from '../static/default-avatar.json'
|
||||
import defaultBaseAvatarItems from '../static/default-base-avatar-items.json'
|
||||
import myProgress from '../static/my-progress.json'
|
||||
import questRewards from '../static/quest-rewards.json'
|
||||
import { getAvatar, setAvatar } from './avatar-db'
|
||||
import {
|
||||
ALL_PLATFORMS,
|
||||
@@ -51,6 +52,7 @@ import {
|
||||
isSpendable,
|
||||
spendCurrency,
|
||||
} from './balance-db'
|
||||
import { getCatalogItem } from './catalog-db'
|
||||
// `LEGACY_CLIENT_BUILD` is shared with the storefront generator rather than restated: it picks
|
||||
// which store FILE a caller is served here, and which ITEMS go in that file there. The two must
|
||||
// name the same moment or a build gets a store built to a different cutoff.
|
||||
@@ -79,7 +81,9 @@ import {
|
||||
BalanceEntry,
|
||||
BulkPurchaseRequest,
|
||||
BulkPurchaseResponse,
|
||||
BuyInventionRequest,
|
||||
BuyInventionResponse,
|
||||
BuyInventionV3Response,
|
||||
BuyItemRequest,
|
||||
BuyItemResponse,
|
||||
ChallengeProgressRequest,
|
||||
@@ -124,6 +128,7 @@ import { claimReward } from './reward-db'
|
||||
import type { Context } from 'hono'
|
||||
import type { GiftContent, Outfit, Progression, StoredGift, XpGrant } from '@repo/domain'
|
||||
import type { CustomAvatarItem } from '../../api/src/custom-avatar-items-db'
|
||||
import type { SavedInvention } from '../../api/src/inventions-db'
|
||||
import type {
|
||||
BalanceResponsePayload,
|
||||
PurchaseBalanceModificationPayload,
|
||||
@@ -165,16 +170,6 @@ async function authedId(c: Context<App>): Promise<number | null> {
|
||||
return validateAndGetAccountId(c.req.raw, await c.env.JWT_SECRET.get())
|
||||
}
|
||||
|
||||
/**
|
||||
* The `role` claim from a Bearer token — the operator-granted roles the auth worker stamps
|
||||
* from the account's flags, so a plain player's token is just `['gameClient']`. `null` when
|
||||
* the request carries no valid token; an empty array means a valid token with no roles.
|
||||
* Shaped to mirror {@link authedId}.
|
||||
*/
|
||||
async function authedRoles(c: Context<App>): Promise<string[] | null> {
|
||||
return validateAndGetRoles(c.req.raw, await c.env.JWT_SECRET.get())
|
||||
}
|
||||
|
||||
/**
|
||||
* The client build this request's token was minted for (`rn.ver`), as a comparable NUMBER —
|
||||
* the leading `YYYYMMDD` of e.g. `20250718.01`, whose `.01` is a same-day rebuild and not a
|
||||
@@ -413,20 +408,32 @@ async function pushBalancePurchase(
|
||||
*/
|
||||
const NOT_AN_INFLUENCER = 0
|
||||
|
||||
/** The operator-granted role that comes with a complimentary subscription. */
|
||||
const DEVELOPER_ROLE = 'developer'
|
||||
|
||||
/**
|
||||
* Whether the caller currently holds a Rec Room Plus subscription — the ONE definition,
|
||||
* shared by `UpdateAndGetSubscription` (which reports it) and the storefront buys (which
|
||||
* price off it via `SubscriberPrices`). Nothing sells subscriptions here, so holding the
|
||||
* `developer` role IS the subscription; if a real subscription store ever lands, this is
|
||||
* the only place that has to learn about it. Read from the token's `role` claim, never the
|
||||
* body; no or an invalid token is "not subscribed".
|
||||
* Whether the caller holds a Rec Room Plus subscription — the ONE definition, shared by
|
||||
* `UpdateAndGetSubscription` (which reports it) and the storefront buys (which price off
|
||||
* it via `SubscriberPrices`). Those two must never disagree: a subscriber whose client
|
||||
* applied the discount itself and then had the buy refused as a price mismatch is exactly
|
||||
* what one definition prevents.
|
||||
*
|
||||
* Nothing SELLS subscriptions here. Plus is `account.hasPlus`, claimed on the website by
|
||||
* proving a qualifying role in the community Discord (`www` `POST /api/benefits/claim`),
|
||||
* and it reaches this worker as the token's `rn.plus` claim — stamped by `auth` at login
|
||||
* from that flag. So this is a pure token read: no database, no binding, nothing to load.
|
||||
*
|
||||
* The cost is FRESHNESS, deliberately accepted. The claim is only as current as the token,
|
||||
* which lasts a day and is never refreshed (see TOKEN_TTL_SECONDS), so a player who claims
|
||||
* on the website has to sign in again — and restart the game — before Plus applies. The
|
||||
* website's claim page says so.
|
||||
*
|
||||
* The `developer` role does NOT grant Plus. It used to, as a stand-in while nothing else
|
||||
* could confer it; now that the Discord claim exists, Plus is one thing with one source.
|
||||
* An operator who wants a developer to have it sets `hasPlus` on their account like
|
||||
* anyone else's.
|
||||
*
|
||||
* Never read from the body. No token, or an invalid one, is "not subscribed".
|
||||
*/
|
||||
async function isSubscriber(c: Context<App>): Promise<boolean> {
|
||||
const roles = await authedRoles(c)
|
||||
return roles?.includes(DEVELOPER_ROLE) ?? false
|
||||
return validateAndGetPlus(c.req.raw, await c.env.JWT_SECRET.get())
|
||||
}
|
||||
|
||||
/** `SubscriptionLevel.Gold`. 1 is Platinum. */
|
||||
@@ -446,20 +453,21 @@ const SUBSCRIPTION_PLATFORM_ALL = -1
|
||||
const STUB_SUBSCRIPTION_ID = 1
|
||||
|
||||
/**
|
||||
* The complimentary subscription a `developer` account reports — Rec Room Plus, which the
|
||||
* client's API calls a `CampusCard`.
|
||||
* The complimentary subscription a subscriber reports — Rec Room Plus, which the client's
|
||||
* API calls a `CampusCard`. See `isSubscriber` for who counts as one: a `developer`, or a
|
||||
* player who claimed `hasPlus` with a Discord role on the website.
|
||||
*
|
||||
* Nothing here sells subscriptions, so holding the role IS the subscription: it's how the
|
||||
* paid-tier surfaces get exercised without a store. Every field is computed per call and
|
||||
* none of it is persisted, so this is not a record of anything — revoking the role revokes
|
||||
* the subscription, and no expiry sweep or renewal exists.
|
||||
* Nothing here sells subscriptions, so holding one of those IS the subscription. Every
|
||||
* field is computed per call and none of it is persisted, so this is not a record of
|
||||
* anything — dropping the role or the flag drops the subscription, and no expiry sweep or
|
||||
* renewal exists.
|
||||
*
|
||||
* `ExpirationDate` is a year out from THIS call rather than a fixed date: a hard-coded one
|
||||
* lapses on a day nobody is expecting, and the client would start showing an expired
|
||||
* subscription with no way to renew it. `IsAutoRenewing` tells the client the same thing.
|
||||
* The dates are milliseconds-precision ISO like the rest of this worker's timestamps.
|
||||
*/
|
||||
function developerSubscription(accountId: number) {
|
||||
function plusSubscription(accountId: number) {
|
||||
const now = new Date()
|
||||
// Calendar arithmetic, not now + 365 days: setUTCFullYear lands on the same date next
|
||||
// year whether or not a leap day falls in between.
|
||||
@@ -1723,6 +1731,87 @@ function toGameRewardDrop(): StoreGiftDrop {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One row of `static/quest-rewards.json`: the reward table of the live game's activities,
|
||||
* keyed by the `giftContext` the client posts with a game-reward ask (`Dodgeball`,
|
||||
* `Quest_Goblin_S`, `Paintball_Dam`, …). Each row is the gift-drop as the game's own reward
|
||||
* server shaped it — a comma-laden `AvatarItemDesc` (the catalog's `item_key`), or for the
|
||||
* Laser Tag entry a currency payout — with `GiftRarity` and the activity's own `Context`
|
||||
* (8000 for dodgeball, 4003 for the goblin quest's S rank) spelled the way the client's box
|
||||
* reads them. Untyped fields (`Id`, `Level`, `Message`) are carried but unused.
|
||||
*/
|
||||
interface QuestReward {
|
||||
AvatarItemDesc: string
|
||||
ConsumableItemDesc: string
|
||||
EquipmentPrefabName: string
|
||||
EquipmentModificationGuid: string
|
||||
CurrencyType: number
|
||||
Currency: number
|
||||
Xp: number
|
||||
GiftRarity: number
|
||||
Context: number
|
||||
}
|
||||
|
||||
const QUEST_REWARDS: Record<string, QuestReward[]> = questRewards
|
||||
|
||||
/**
|
||||
* The reward an activity pays, when `giftContext` names an entry in `quest-rewards.json`:
|
||||
* one row drawn at random from that key's list, among the rows the player DOESN'T ALREADY
|
||||
* OWN — the table is "what this activity can give you", and handing over a duplicate gives
|
||||
* nothing (the inventory is a set). A currency row is never "owned", so it always stays in
|
||||
* the pool.
|
||||
*
|
||||
* Null for a context the table doesn't know (or one whose every reward the player already
|
||||
* has), which the caller pays as the plain XP box — the cooldown key is the same string
|
||||
* either way, so an unknown or exhausted context is still rate-limited.
|
||||
*/
|
||||
async function pickQuestReward(
|
||||
db: D1Database,
|
||||
accountId: number,
|
||||
giftContext: string
|
||||
): Promise<QuestReward | null> {
|
||||
if (!Object.hasOwn(QUEST_REWARDS, giftContext)) return null
|
||||
const rows = QUEST_REWARDS[giftContext] ?? []
|
||||
if (rows.length === 0) return null
|
||||
const ownedItems = new Set((await getInventory(db, accountId)).map((i) => i.AvatarItemDesc))
|
||||
const ownedGuids = new Set((await getEquipment(db, accountId)).map((e) => e.ModificationGuid))
|
||||
const pool = rows.filter(
|
||||
(r) =>
|
||||
!(r.AvatarItemDesc !== '' && ownedItems.has(r.AvatarItemDesc)) &&
|
||||
!(r.EquipmentModificationGuid !== '' && ownedGuids.has(r.EquipmentModificationGuid))
|
||||
)
|
||||
if (pool.length === 0) {
|
||||
logger.info('quest rewards exhausted for player', { accountId, giftContext })
|
||||
return null
|
||||
}
|
||||
return pool[Math.floor(Math.random() * pool.length)] ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* A quest reward as the gift-drop `grantGiftDrop` hands over. The item fields come off the
|
||||
* row, so the item IS granted — unlike {@link toGameRewardDrop}'s empty box. The catalog
|
||||
* row for the item, when it resolves, supplies what the table doesn't carry (name, tooltip,
|
||||
* `AvatarItemType`), so the inventory entry reads like a bought one rather than blank.
|
||||
* The XP is the flat game-reward amount, not the row's (always 0): the reward is the item,
|
||||
* and the XP is the same pat on the back every claim gets.
|
||||
*/
|
||||
function toQuestRewardDrop(reward: QuestReward, catalog: CatalogRow | null): StoreGiftDrop {
|
||||
return {
|
||||
FriendlyName: catalog?.friendly_name ?? '',
|
||||
Tooltip: catalog?.tooltip ?? '',
|
||||
ConsumableItemDesc: reward.ConsumableItemDesc,
|
||||
AvatarItemDesc: reward.AvatarItemDesc,
|
||||
AvatarItemType: catalog?.avatar_item_type ?? null,
|
||||
EquipmentPrefabName: reward.EquipmentPrefabName,
|
||||
EquipmentModificationGuid: reward.EquipmentModificationGuid,
|
||||
Rarity: reward.GiftRarity,
|
||||
Context: reward.Context,
|
||||
Currency: reward.Currency,
|
||||
CurrencyType: reward.CurrencyType,
|
||||
Xp: GAME_REWARD_XP,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The box a CLOTHING level-up hands over: a query drop at the level's own tier, rolled from
|
||||
* AVATAR ITEMS only. The published table calls these levels "N-Star Clothing", so the prize
|
||||
@@ -2011,6 +2100,120 @@ function listRoute(summary: string, description: string, auth = false) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed invention purchase: the invention that changed hands and the buyer's
|
||||
* RESULTING token balance (not the change — see the envelopes both routes build from it).
|
||||
*/
|
||||
interface SettledInventionPurchase {
|
||||
invention: SavedInvention
|
||||
balance: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Settle an invention purchase — the whole of buyInvention except the envelope it is
|
||||
* announced in, shared by the `v2` GET and the `v3` POST. Every refusal is a `Response`
|
||||
* (the `{ error }` body both routes answer with); a sale is the bought invention and the
|
||||
* buyer's resulting balance, which each route then wraps in ITS OWN shape — the two
|
||||
* clients want different ones, so the money is shared and the projection is not.
|
||||
*
|
||||
* A priced invention is settled player-to-player: the buyer is debited its `Price` in
|
||||
* RecCenterTokens and the CREATOR is credited the same amount — no house cut, so the
|
||||
* tokens are moved rather than minted or burned. A free invention (`Price` 0) skips the
|
||||
* money entirely: nothing is debited and nobody is paid. The stored price is confirmed
|
||||
* against the price the client rendered first, so a stale or tampered client can’t buy
|
||||
* at a price the creator no longer offers (409), and an unaffordable one is a 400 —
|
||||
* the same "Insufficient balance" buyItem answers with.
|
||||
*
|
||||
* Ownership is recorded in `inventory_invention`; the creator is not sold their own
|
||||
* invention (they own it already, via CreatorPlayerId) and a re-buy is a 409 rather
|
||||
* than a second row. The invention’s `NumDownloads` counter is deliberately NOT
|
||||
* bumped: that column lives on the `invention` table the `api` worker owns, and this
|
||||
* worker only reads it.
|
||||
*/
|
||||
async function settleInventionPurchase(
|
||||
c: Context<App>,
|
||||
id: number,
|
||||
inventionId: number,
|
||||
requestedPrice: number
|
||||
): Promise<SettledInventionPurchase | Response> {
|
||||
const invention = await getInventionById(c.env.DB, inventionId)
|
||||
if (invention === null) return c.json({ error: 'Invention not found' }, 404)
|
||||
// An unpublished invention is a draft: it isn't on sale, not even for free.
|
||||
if (!invention.IsPublished) return c.json({ error: 'Invention is not for sale' }, 403)
|
||||
if (invention.CreatorPlayerId === id) {
|
||||
return c.json({ error: 'Cannot buy your own invention' }, 400)
|
||||
}
|
||||
if (await ownsInvention(c.env.DB, id, inventionId)) {
|
||||
return c.json({ error: 'Already owned' }, 409)
|
||||
}
|
||||
|
||||
// The price the client rendered must still be the stored one: a mismatch is a stale
|
||||
// catalog or a tampered request, never a sale.
|
||||
if (invention.Price !== requestedPrice) {
|
||||
return c.json({ error: 'Price has changed' }, 409)
|
||||
}
|
||||
|
||||
const startingTokens = intVar(c.env.STARTING_TOKENS, DEFAULT_STARTING_TOKENS)
|
||||
// Inventions are priced in RecCenterTokens only — the store shows no other currency
|
||||
// for them, and `Price` carries no currency of its own to pick a different one from.
|
||||
const price = invention.Price
|
||||
if (price > 0) {
|
||||
// Debit the buyer atomically; false means they couldn't afford it and nothing
|
||||
// changed, so no ownership is recorded and the creator is not paid.
|
||||
const paid = await spendCurrency(
|
||||
c.env.DB,
|
||||
id,
|
||||
CurrencyType.RecCenterTokens,
|
||||
price,
|
||||
startingTokens
|
||||
)
|
||||
if (!paid) return c.json({ error: 'Insufficient balance' }, 400)
|
||||
}
|
||||
|
||||
// Grant before paying out: these are three separate D1 writes with no transaction
|
||||
// around them, so order them by what a failure costs. A buyer who paid and got the
|
||||
// invention but left the creator unpaid is recoverable; a buyer charged for nothing
|
||||
// is not.
|
||||
await grantInvention(c.env.DB, id, inventionId)
|
||||
|
||||
if (price > 0) {
|
||||
// Seed the creator's signup grant BEFORE crediting them: `creditCurrency` upserts
|
||||
// the balance row, and `ensureStartingBalances` is an INSERT OR IGNORE, so a
|
||||
// creator who had never touched their balance would otherwise have the row created
|
||||
// here and lose their starting tokens forever.
|
||||
await ensureStartingBalances(c.env.DB, invention.CreatorPlayerId, startingTokens)
|
||||
const creatorBalance = await creditCurrency(
|
||||
c.env.DB,
|
||||
invention.CreatorPlayerId,
|
||||
CurrencyType.RecCenterTokens,
|
||||
price,
|
||||
startingTokens
|
||||
)
|
||||
// The creator is a different, probably-online player with no response to read:
|
||||
// push the sale so it lands on their shown balance without a re-fetch. The frame
|
||||
// carries their resulting TOTAL (what `creditCurrency` returns), not the payout —
|
||||
// sending the payout would set their whole balance to it. A plain update rather
|
||||
// than a purchase frame: they sold, they didn't buy. Best-effort, as everywhere.
|
||||
await pushBalanceUpdate(
|
||||
c,
|
||||
invention.CreatorPlayerId,
|
||||
CurrencyType.RecCenterTokens,
|
||||
creatorBalance
|
||||
)
|
||||
}
|
||||
|
||||
// Unlike buyItem — whose `Balance` is the change applied — the reference server
|
||||
// answers this one with the RESULTING total (a first read seeds the buyer's starting
|
||||
// grant, as everywhere else). The buyer's frame carries that same total, so the body
|
||||
// and the push land the client on one number.
|
||||
const balance = await getBalance(c.env.DB, id, CurrencyType.RecCenterTokens, startingTokens)
|
||||
// A free invention moved nothing, so there is no purchase to report.
|
||||
if (price > 0) {
|
||||
await pushBalancePurchase(c, id, CurrencyType.RecCenterTokens, -price, balance)
|
||||
}
|
||||
return { invention, balance }
|
||||
}
|
||||
|
||||
// strict: false so trailing-slash routes (e.g. `/gifts/consume/`, which the client
|
||||
// posts with a trailing slash) match either form. Mirrors the `api` worker.
|
||||
const app = new Hono<App>({ strict: false })
|
||||
@@ -3014,8 +3217,8 @@ const app = new Hono<App>({ strict: false })
|
||||
summary: 'Buy a storefront item',
|
||||
description: [
|
||||
'Looks the item up in its storefront catalog, confirms the client’s `RequestedPrice`',
|
||||
'still matches the `Prices` entry — a Rec Room Plus subscriber (the same check as',
|
||||
'`UpdateAndGetSubscription`) may pay anywhere from that down to 10% off, since their',
|
||||
'still matches the `Prices` entry — a Rec Room Plus subscriber (the same `rn.plus`',
|
||||
'check as `UpdateAndGetSubscription`) may pay anywhere from that down to 10% off, since their',
|
||||
'client applies the discount itself and not to every item — debits the buyer atomically,',
|
||||
'grants the item (into the inventory or',
|
||||
'consumable table), and returns a gift box. A `Gift` block routes the item — and its',
|
||||
@@ -3425,22 +3628,11 @@ const app = new Hono<App>({ strict: false })
|
||||
}
|
||||
)
|
||||
|
||||
// Buy an invention. [Authorize]. A GET, despite being a purchase — the client sends
|
||||
// `?inventionId=…&requestedPrice=…` with no body, so that's what we answer.
|
||||
//
|
||||
// A priced invention is settled player-to-player: the buyer is debited its `Price` in
|
||||
// RecCenterTokens and the CREATOR is credited the same amount — no house cut, so the
|
||||
// tokens are moved rather than minted or burned. A free invention (`Price` 0) skips the
|
||||
// money entirely: nothing is debited and nobody is paid. The stored price is confirmed
|
||||
// against the price the client rendered first, so a stale or tampered client can't buy
|
||||
// at a price the creator no longer offers (409), and an unaffordable one is a 400 —
|
||||
// the same "Insufficient balance" buyItem answers with.
|
||||
//
|
||||
// Ownership is recorded in `inventory_invention`; the creator is not sold their own
|
||||
// invention (they own it already, via CreatorPlayerId) and a re-buy is a 409 rather
|
||||
// than a second row. The invention's `NumDownloads` counter is deliberately NOT
|
||||
// bumped: that column lives on the `invention` table the `api` worker owns, and this
|
||||
// worker only reads it.
|
||||
// Buy an invention. [Authorize]. A GET, despite being a purchase — the 2023 client sends
|
||||
// `?inventionId=…&requestedPrice=…` with no body, so that’s what we answer, in the v6 save
|
||||
// envelope that build reads. The 2025 build posts to `v3/buyInvention` below and wants a
|
||||
// different envelope back; the two share {@link settleInventionPurchase}, which is where
|
||||
// the money and the rules live, and build their own bodies from what it returns.
|
||||
.get(
|
||||
'/api/storefronts/v2/buyInvention',
|
||||
describeRoute({
|
||||
@@ -3495,91 +3687,116 @@ const app = new Hono<App>({ strict: false })
|
||||
// a priced one then fails the confirmation below rather than selling for nothing.
|
||||
const requestedPrice = Number.parseInt(c.req.query('requestedPrice') ?? '0', 10) || 0
|
||||
|
||||
const invention = await getInventionById(c.env.DB, inventionId)
|
||||
if (invention === null) return c.json({ error: 'Invention not found' }, 404)
|
||||
// An unpublished invention is a draft: it isn't on sale, not even for free.
|
||||
if (!invention.IsPublished) return c.json({ error: 'Invention is not for sale' }, 403)
|
||||
if (invention.CreatorPlayerId === id) {
|
||||
return c.json({ error: 'Cannot buy your own invention' }, 400)
|
||||
}
|
||||
if (await ownsInvention(c.env.DB, id, inventionId)) {
|
||||
return c.json({ error: 'Already owned' }, 409)
|
||||
}
|
||||
const settled = await settleInventionPurchase(c, id, inventionId, requestedPrice)
|
||||
if (settled instanceof Response) return settled
|
||||
|
||||
// The price the client rendered must still be the stored one: a mismatch is a stale
|
||||
// catalog or a tampered request, never a sale.
|
||||
if (invention.Price !== requestedPrice) {
|
||||
return c.json({ error: 'Price has changed' }, 409)
|
||||
}
|
||||
|
||||
const startingTokens = intVar(c.env.STARTING_TOKENS, DEFAULT_STARTING_TOKENS)
|
||||
// Inventions are priced in RecCenterTokens only — the store shows no other currency
|
||||
// for them, and `Price` carries no currency of its own to pick a different one from.
|
||||
const price = invention.Price
|
||||
if (price > 0) {
|
||||
// Debit the buyer atomically; false means they couldn't afford it and nothing
|
||||
// changed, so no ownership is recorded and the creator is not paid.
|
||||
const paid = await spendCurrency(
|
||||
c.env.DB,
|
||||
id,
|
||||
CurrencyType.RecCenterTokens,
|
||||
price,
|
||||
startingTokens
|
||||
)
|
||||
if (!paid) return c.json({ error: 'Insufficient balance' }, 400)
|
||||
}
|
||||
|
||||
// Grant before paying out: these are three separate D1 writes with no transaction
|
||||
// around them, so order them by what a failure costs. A buyer who paid and got the
|
||||
// invention but left the creator unpaid is recoverable; a buyer charged for nothing
|
||||
// is not.
|
||||
await grantInvention(c.env.DB, id, inventionId)
|
||||
|
||||
if (price > 0) {
|
||||
// Seed the creator's signup grant BEFORE crediting them: `creditCurrency` upserts
|
||||
// the balance row, and `ensureStartingBalances` is an INSERT OR IGNORE, so a
|
||||
// creator who had never touched their balance would otherwise have the row created
|
||||
// here and lose their starting tokens forever.
|
||||
await ensureStartingBalances(c.env.DB, invention.CreatorPlayerId, startingTokens)
|
||||
const creatorBalance = await creditCurrency(
|
||||
c.env.DB,
|
||||
invention.CreatorPlayerId,
|
||||
CurrencyType.RecCenterTokens,
|
||||
price,
|
||||
startingTokens
|
||||
)
|
||||
// The creator is a different, probably-online player with no response to read:
|
||||
// push the sale so it lands on their shown balance without a re-fetch. The frame
|
||||
// carries their resulting TOTAL (what `creditCurrency` returns), not the payout —
|
||||
// sending the payout would set their whole balance to it. A plain update rather
|
||||
// than a purchase frame: they sold, they didn't buy. Best-effort, as everywhere.
|
||||
await pushBalanceUpdate(
|
||||
c,
|
||||
invention.CreatorPlayerId,
|
||||
CurrencyType.RecCenterTokens,
|
||||
creatorBalance
|
||||
)
|
||||
}
|
||||
|
||||
// Unlike buyItem — whose `Balance` is the change applied — the reference server
|
||||
// answers this one with the RESULTING total (a first read seeds the buyer's starting
|
||||
// grant, as everywhere else). The buyer's frame carries that same total, so the body
|
||||
// and the push land the client on one number.
|
||||
const balance = await getBalance(c.env.DB, id, CurrencyType.RecCenterTokens, startingTokens)
|
||||
// A free invention moved nothing, so there is no purchase to report.
|
||||
if (price > 0) {
|
||||
await pushBalancePurchase(c, id, CurrencyType.RecCenterTokens, -price, balance)
|
||||
}
|
||||
return c.json({
|
||||
BalanceUpdateResponse: {
|
||||
Balance: balance,
|
||||
Balance: settled.balance,
|
||||
BalanceType: ALL_PLATFORMS,
|
||||
CurrencyType: CurrencyType.RecCenterTokens,
|
||||
BalanceUpdates: [{ UpdateResponse: 0, Data: invention }],
|
||||
BalanceUpdates: [{ UpdateResponse: 0, Data: settled.invention }],
|
||||
},
|
||||
// The bare `{ Status, Invention, InventionVersion }` the v6 save serves — this
|
||||
// build's invention endpoints answer in it, and the client re-renders from it.
|
||||
InventionResponse: toSaveResult(settled.invention),
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Buy an invention, the way the 2025 client asks for it. [Authorize]. A POST carrying
|
||||
// `{ InventionId, RequestedPrice }` as JSON, where the v2 GET takes query params.
|
||||
//
|
||||
// The PURCHASE is identical — both settle through `settleInventionPurchase` — but the
|
||||
// RESPONSE is not, and that is the whole reason this route exists rather than an alias:
|
||||
// this build wraps the invention in the v9 save envelope and names its balance bucket
|
||||
// `Platform`. See `BuyInventionV3Response`. Both routes stay served: the 2023 build still
|
||||
// sends the GET, and it would not parse this body.
|
||||
.post(
|
||||
'/api/storefronts/v3/buyInvention',
|
||||
describeRoute({
|
||||
tags: ['Storefront'],
|
||||
summary: 'Buy an invention (JSON body)',
|
||||
description: [
|
||||
'The same purchase as `GET /api/storefronts/v2/buyInvention` — confirms the client’s',
|
||||
'`RequestedPrice` still matches the invention’s stored `Price`, debits the buyer and',
|
||||
'pays the creator that price in RecCenterTokens (a free invention moves nothing),',
|
||||
'records ownership in `inventory_invention`, and pushes both players a socket frame',
|
||||
'carrying their RESULTING total — but answered in a DIFFERENT envelope, which is why',
|
||||
'the route exists at all: `InventionResponse` is the v9 save’s',
|
||||
'`{ Value, Success, Error, error_id }` (its `InventionVersion` and `TagsResponse` null,',
|
||||
'since a buy mints neither) and the balance half names its bucket `Platform`, not v2’s',
|
||||
'`BalanceType`.',
|
||||
].join(' '),
|
||||
security: AUTHED,
|
||||
requestBody: jsonBody(BuyInventionRequest, 'The invention id and the price rendered'),
|
||||
responses: {
|
||||
200: json(BuyInventionV3Response, 'The purchase result (invention + balance)'),
|
||||
400: json(
|
||||
ErrorResponse,
|
||||
'Invalid body, missing InventionId, buying your own, or insufficient balance'
|
||||
),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
403: json(ErrorResponse, 'The invention is not published, so it is not for sale'),
|
||||
404: json(ErrorResponse, 'No such invention'),
|
||||
409: json(ErrorResponse, 'Already owned, or the price has changed'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
|
||||
const body = (await c.req.json().catch(() => null)) as Record<string, unknown> | null
|
||||
if (body === null || typeof body !== 'object' || Array.isArray(body)) {
|
||||
return c.json({ error: 'Invalid request body' }, 400)
|
||||
}
|
||||
const inventionId = body.InventionId
|
||||
if (!Number.isInteger(inventionId)) {
|
||||
return c.json({ error: 'InventionId is required' }, 400)
|
||||
}
|
||||
// Read the same way as v2’s query param: an absent or non-integer RequestedPrice is 0,
|
||||
// which only matches a free invention — a priced one then fails the confirmation rather
|
||||
// than selling for nothing.
|
||||
const requestedPrice = Number.isInteger(body.RequestedPrice)
|
||||
? (body.RequestedPrice as number)
|
||||
: 0
|
||||
|
||||
const settled = await settleInventionPurchase(c, id, inventionId as number, requestedPrice)
|
||||
if (settled instanceof Response) return settled
|
||||
|
||||
return c.json({
|
||||
// The v9 SAVE envelope, not v6's bare `{ Status, Invention, InventionVersion }`:
|
||||
// `Value` under `{ Success, Error, error_id }`, with `Invention` the client's 28-key
|
||||
// `RRInvention`. A buy mints no version and takes no tags, so both of those keys are
|
||||
// present and NULL — which is safe here for the same reason it is on the save: the
|
||||
// client reads `Success` and `Value.Invention` and nothing else. `Value` itself must
|
||||
// never be null under `Success: true` — that dereference is what crashes it.
|
||||
InventionResponse: {
|
||||
Value: {
|
||||
Status: 0,
|
||||
Invention: toInventionV9(settled.invention),
|
||||
InventionVersion: null,
|
||||
TagsResponse: null,
|
||||
},
|
||||
Success: true,
|
||||
Error: null,
|
||||
error_id: null,
|
||||
},
|
||||
// `BalanceResponseDTO`, the same one the bulk purchase answers in — so the bucket key
|
||||
// is `Platform`, NOT the `BalanceType` the v2 body sends. The client's member IS named
|
||||
// `BalanceType`, but it carries a [DataMember] rename to `Platform` and its decoder
|
||||
// drops what it doesn't know, so spelling it `BalanceType` here would land this balance
|
||||
// in bucket 0 beside the real one. `Balance` is the RESULTING total, as in v2.
|
||||
BalanceUpdateResponse: {
|
||||
BalanceUpdates: [{ UpdateResponse: 0, Data: toInventionV9(settled.invention) }],
|
||||
Balance: settled.balance,
|
||||
CurrencyType: CurrencyType.RecCenterTokens,
|
||||
// The capture says 0 (SteamPurchased) because the reference server kept a wallet per
|
||||
// platform. This one keeps ONE bucket and the client SUMS them, so naming 0 here
|
||||
// while every socket frame names -2 is exactly the phantom second balance that
|
||||
// doubled players' tokens twice before. -2, like every other surface.
|
||||
Platform: ALL_PLATFORMS,
|
||||
},
|
||||
// The same `{ Status, Invention, InventionVersion }` envelope the invention
|
||||
// save/read endpoints serve — the client re-renders the invention from it.
|
||||
InventionResponse: toSaveResult(invention),
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -3737,6 +3954,11 @@ const app = new Hono<App>({ strict: false })
|
||||
// activity of the day is per ACTIVITY, so a player who moves from Soccer to Paintball is
|
||||
// owed another reward while a second Soccer match inside the hour is not. An ask that
|
||||
// sends no context keys on `''`.
|
||||
//
|
||||
// It also picks the PRIZE: a context that is a key of `static/quest-rewards.json`
|
||||
// (`Dodgeball`, `Quest_Goblin_S`, …) draws one of that activity's rewards — an avatar item
|
||||
// granted into the inventory, or Laser Tag's ticket payout — and the box carries it, with
|
||||
// the activity's own `GiftContext`. A context the table doesn't know gets the XP-only box.
|
||||
.post(
|
||||
'/api/gamerewards/v1/request',
|
||||
describeRoute({
|
||||
@@ -3746,8 +3968,10 @@ const app = new Hono<App>({ strict: false })
|
||||
'Claims one reward of `rewardType` in `giftContext` per hour per player, recorded in',
|
||||
'`reward_status`. The cooldown is per (type, activity), so a different activity is',
|
||||
'owed another reward while the same one is not; an ask with no `giftContext` keys on',
|
||||
'the empty context. The reward rides in a gift box, so a claim and a rejected',
|
||||
'(on-cooldown) ask both answer `[]`.',
|
||||
'the empty context. A `giftContext` that names an activity in `quest-rewards.json`',
|
||||
'(`Dodgeball`, `Quest_Goblin_S`, …) draws one of that activity’s rewards and grants it;',
|
||||
'any other claim pays XP only. The reward rides in a gift box, so a claim and a',
|
||||
'rejected (on-cooldown) ask both answer `[]`.',
|
||||
].join(' '),
|
||||
security: AUTHED,
|
||||
requestBody: form(GameRewardRequest, 'The reward type and its display message'),
|
||||
@@ -3774,7 +3998,30 @@ const app = new Hono<App>({ strict: false })
|
||||
// Bank the XP first: it is the reward, and the box is the wrapper the client shows.
|
||||
// A failure here must not leave a box promising XP that was never credited.
|
||||
const { progression, levelsGained } = await addXp(c.env.DB, id, GAME_REWARD_XP)
|
||||
const granted = await grantGiftDrop(c, id, toGameRewardDrop(), message)
|
||||
// An activity the reward table knows pays one of ITS rewards the player lacks — the
|
||||
// item rides in the box and is granted with it. Anything else gets the plain XP box.
|
||||
const questReward = await pickQuestReward(c.env.DB, id, giftContext)
|
||||
const itemKey = questReward?.AvatarItemDesc || questReward?.EquipmentModificationGuid
|
||||
const drop =
|
||||
questReward === null
|
||||
? toGameRewardDrop()
|
||||
: toQuestRewardDrop(questReward, itemKey ? await getCatalogItem(c.env.DB, itemKey) : null)
|
||||
// A currency reward (Laser Tag's tickets) is credited here: `grantGiftDrop` grants
|
||||
// items, not balances. Seed the signup grant first — `creditCurrency` upserts the
|
||||
// row, and a never-touched RecCenterTokens balance would otherwise lose it.
|
||||
if (drop.Currency > 0 && drop.CurrencyType !== CurrencyType.Invalid) {
|
||||
const startingTokens = intVar(c.env.STARTING_TOKENS, DEFAULT_STARTING_TOKENS)
|
||||
await ensureStartingBalances(c.env.DB, id, startingTokens)
|
||||
const balance = await creditCurrency(
|
||||
c.env.DB,
|
||||
id,
|
||||
drop.CurrencyType,
|
||||
drop.Currency,
|
||||
startingTokens
|
||||
)
|
||||
await pushBalanceUpdate(c, id, drop.CurrencyType, balance)
|
||||
}
|
||||
const granted = await grantGiftDrop(c, id, drop, message)
|
||||
await pushGiftReceived(c, id, granted, message, COACH_ACCOUNT_ID)
|
||||
// Every grant moves the bar, whether or not it crossed a level.
|
||||
await pushProgressionUpdate(c, id, progression)
|
||||
@@ -3834,26 +4081,29 @@ const app = new Hono<App>({ strict: false })
|
||||
)
|
||||
|
||||
// Subscription lookup (Rec Room Plus, the client's `CampusCard`). There is no store to
|
||||
// buy one from, so the `developer` role stands in for a paid subscription: a developer
|
||||
// reports an active Gold year, everyone else reports none. Nothing is stored — see
|
||||
// `developerSubscription`.
|
||||
// buy one from: Plus is claimed on the website by proving a Discord role, and reaches
|
||||
// this worker as the token's `rn.plus` claim. A caller carrying it reports an active
|
||||
// Gold year; everyone else reports none. Nothing about the subscription itself is
|
||||
// stored, and nothing here reads the database — see `isSubscriber` and `plusSubscription`.
|
||||
//
|
||||
// Auth is OPTIONAL, and a missing or invalid token answers "no subscription" rather than
|
||||
// 401: the client posts this while loading, so an error here can stall its load
|
||||
// orchestration, and "you aren't subscribed" is the truthful answer for an anonymous
|
||||
// caller anyway. The role is read from the token's `role` claim, never from the body.
|
||||
// caller anyway. Never read from the body.
|
||||
.post(
|
||||
'/api/CampusCard/v1/UpdateAndGetSubscription',
|
||||
describeRoute({
|
||||
tags: ['Econ'],
|
||||
summary: 'Subscription lookup',
|
||||
description: [
|
||||
'The caller’s Rec Room Plus subscription. Nothing sells subscriptions here, so the',
|
||||
'operator-granted `developer` role stands in for one: a developer’s token reports an',
|
||||
'active Gold (`Level` 0) yearly (`Period` 1) subscription on `PlatformType` -1 (All),',
|
||||
'expiring a year from the call, and every other caller gets `{}`. Auth is optional —',
|
||||
'a missing or invalid token reads as “not subscribed”, not 401. Nothing is persisted:',
|
||||
'the role IS the subscription, so revoking it revokes this.',
|
||||
'The caller’s Rec Room Plus subscription. Nothing sells subscriptions here: Plus is',
|
||||
'claimed on the website by proving a qualifying role in the community Discord, and',
|
||||
'arrives as the token’s `rn.plus` claim. A token carrying it reports an active Gold',
|
||||
'(`Level` 0) yearly (`Period` 1) subscription on `PlatformType` -1 (All), expiring a',
|
||||
'year from the call; every other caller gets `{}`. The `developer` role does NOT',
|
||||
'confer it. Auth is optional — a missing or invalid token reads as “not subscribed”,',
|
||||
'not 401. The subscription itself is not persisted, and because the claim is stamped',
|
||||
'at login, a player who has just claimed must sign in again before it appears.',
|
||||
].join(' '),
|
||||
responses: {
|
||||
200: json(SubscriptionResponse, 'The subscription, or `{}` for no subscription'),
|
||||
@@ -3864,7 +4114,7 @@ const app = new Hono<App>({ strict: false })
|
||||
const id = await authedId(c)
|
||||
if (id === null) return c.json({})
|
||||
return c.json({
|
||||
Subscription: developerSubscription(id),
|
||||
Subscription: plusSubscription(id),
|
||||
PlatformAccountSubscribedPlayerId: null,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,8 +116,9 @@ export const CustomAvatarItemsResponse = z.object({
|
||||
|
||||
/**
|
||||
* A Rec Room Plus subscription (the client calls it a `CampusCard`). Nothing here sells one,
|
||||
* so this is the complimentary subscription a `developer` account reports — see
|
||||
* `developerSubscription` in econ.app.ts for why each field reads the way it does.
|
||||
* so this is the complimentary subscription reported by a caller whose token carries
|
||||
* `rn.plus` — stamped from `account.hasPlus`, which the website's Discord benefits claim
|
||||
* sets. See `plusSubscription` in econ.app.ts for why each field reads the way it does.
|
||||
*/
|
||||
export const SubscriptionDto = z.object({
|
||||
SubscriptionId: z.int().describe('Placeholder — no subscription is stored'),
|
||||
@@ -374,10 +375,24 @@ export const BulkPurchaseResponse = z.object({
|
||||
})
|
||||
|
||||
/**
|
||||
* `GET /api/storefronts/v2/buyInvention` — the purchase result. Two envelopes side by
|
||||
* side: the balance update (shaped like buyItem's, except `Balance` is the RESULTING
|
||||
* total, not the change, and `Data` is a single invention rather than a gift-drop list)
|
||||
* and the invention envelope the invention endpoints already serve.
|
||||
* The JSON body `POST /api/storefronts/v3/buyInvention` takes. The same two values the v2
|
||||
* GET reads off the query string (`inventionId`/`requestedPrice`), PascalCase in a body —
|
||||
* that is the only difference between the two routes.
|
||||
*/
|
||||
export const BuyInventionRequest = z.object({
|
||||
InventionId: z.int().describe('The invention to buy; missing or non-integer is 400'),
|
||||
RequestedPrice: z
|
||||
.int()
|
||||
.optional()
|
||||
.describe('The price the client rendered; a mismatch is 409. Absent reads as 0'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `GET /api/storefronts/v2/buyInvention` and `POST /api/storefronts/v3/buyInvention` — the
|
||||
* purchase result, identical for both. Two envelopes side by side: the balance update
|
||||
* (shaped like buyItem's, except `Balance` is the RESULTING total, not the change, and
|
||||
* `Data` is a single invention rather than a gift-drop list) and the invention envelope the
|
||||
* invention endpoints already serve.
|
||||
*/
|
||||
export const BuyInventionResponse = z.object({
|
||||
BalanceUpdateResponse: z.object({
|
||||
@@ -400,6 +415,54 @@ export const BuyInventionResponse = z.object({
|
||||
.describe('The same envelope `POST /api/inventions/v6/save` returns'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `POST /api/storefronts/v3/buyInvention` — the purchase result the 2025 client wants,
|
||||
* which is NOT v2's despite settling the identical purchase. Two differences, both
|
||||
* recovered from a capture of the real response:
|
||||
*
|
||||
* - `InventionResponse` is the v9 SAVE envelope (`{ Value, Success, Error, error_id }`)
|
||||
* rather than v6's bare `{ Status, Invention, InventionVersion }`, and its `Invention`
|
||||
* is the client's 28-key `RRInvention`. A buy mints no version and takes no tags, so
|
||||
* `InventionVersion` and `TagsResponse` are present and null.
|
||||
* - The balance half is `BalanceResponseDTO`, so the bucket key is `Platform` — the
|
||||
* client's `BalanceType` member under a [DataMember] rename, the same one the bulk
|
||||
* purchase answers in. v2 spells it `BalanceType`; do not unify them.
|
||||
*/
|
||||
export const BuyInventionV3Response = z.object({
|
||||
InventionResponse: z
|
||||
.object({
|
||||
Value: z
|
||||
.object({
|
||||
Status: z.int().describe('0 on success'),
|
||||
Invention: JsonObject.describe('The bought invention as the 28-key `RRInvention`'),
|
||||
InventionVersion: z.null().describe('Always null — a buy mints no version'),
|
||||
TagsResponse: z.null().describe('Always null — a buy takes no tags'),
|
||||
})
|
||||
.describe('Never null under `Success: true` — the client dereferences it unguarded'),
|
||||
Success: z.boolean(),
|
||||
Error: z.string().nullable().describe('Null on success — not `""`'),
|
||||
error_id: z.string().nullable().describe('Always null — no error-id catalog here'),
|
||||
})
|
||||
.describe('The same envelope `POST /api/inventions/v9/save` returns'),
|
||||
BalanceUpdateResponse: z.object({
|
||||
BalanceUpdates: z.array(
|
||||
z.object({
|
||||
UpdateResponse: z.int(),
|
||||
Data: JsonObject.describe('The bought invention, the same `RRInvention` as above'),
|
||||
})
|
||||
),
|
||||
Balance: z.int().describe('The resulting balance — NOT the change, unlike buyItem'),
|
||||
CurrencyType: z.int().describe('2 = RecCenterTokens'),
|
||||
Platform: z
|
||||
.int()
|
||||
.describe(
|
||||
'The balance bucket — the client’s `BalanceType` under a [DataMember] rename. -2, ' +
|
||||
'account-wide: the capture said 0 (SteamPurchased) because the reference server ' +
|
||||
'kept a wallet per platform; this one keeps a single bucket, and the client SUMS them'
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
/** buyItem / buyInvention error body (`{ error }`), returned on 400/403/404/409. */
|
||||
/** The JSON body `POST /api/ugcPurchasables/v1/items/bulk` takes. */
|
||||
export const UgcPurchasableBulkRequest = z.object({
|
||||
@@ -592,7 +655,9 @@ export const GameRewardRequest = z.object({
|
||||
giftContext: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('The activity it came from, e.g. `Soccer` — part of the cooldown key'),
|
||||
.describe(
|
||||
'The activity it came from, e.g. `Soccer` — part of the cooldown key. A key of `quest-rewards.json` (`Dodgeball`, `Quest_Goblin_S`, …) also picks the prize from that activity’s table'
|
||||
),
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,7 @@ import avatarItemsJson from '../../../static/db/avatar-items.json'
|
||||
// caller's build, and these assertions are about the file's CONTENTS.
|
||||
import carriedItems from '../../../static/db/consumables.json'
|
||||
import skinsJson from '../../../static/db/skins.json'
|
||||
import questRewards from '../../../static/quest-rewards.json'
|
||||
import sf32025 from '../../../static/storefronts/sf3-2025.json'
|
||||
import sf3 from '../../../static/storefronts/sf3.json'
|
||||
import { SCHEMA_DDL } from '../../avatar-db'
|
||||
@@ -284,12 +285,21 @@ async function bearer(
|
||||
sub = '42',
|
||||
roles?: string[],
|
||||
/** The client build to stamp as `rn.ver` — omitted, like a token minted before the claim. */
|
||||
version?: string
|
||||
version?: string,
|
||||
/**
|
||||
* Stamp `rn.plus`, as auth does for an account with `hasPlus`. This is the ONLY thing
|
||||
* that makes a caller a Rec Room Plus subscriber — the `developer` role does not — so
|
||||
* every subscriber-priced test passes it.
|
||||
*/
|
||||
plus = false
|
||||
): Promise<Record<string, string>> {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const claims: Record<string, unknown> = { sub, exp: now + 3600 }
|
||||
if (roles !== undefined) claims.role = roles
|
||||
if (version !== undefined) claims['rn.ver'] = version
|
||||
// Omitted when false, exactly as generateToken omits it — so these tokens match the
|
||||
// shape of a real non-subscriber's.
|
||||
if (plus) claims['rn.plus'] = true
|
||||
const signingInput = `${b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))}.${b64url(
|
||||
JSON.stringify(claims)
|
||||
)}`
|
||||
@@ -1769,7 +1779,7 @@ describe('econ endpoints', () => {
|
||||
|
||||
test('POST /api/storefronts/v2/buyItem charges a subscriber the SubscriberPrices entry', async () => {
|
||||
await drainFrames()
|
||||
const res = await buy2263(await bearer('322', ['gameClient', 'developer']), 85)
|
||||
const res = await buy2263(await bearer('322', ['gameClient'], undefined, true), 85)
|
||||
expect(res.status).toBe(200)
|
||||
expect(((await res.json()) as { Balance: number }).Balance).toBe(-85)
|
||||
const bal = await exports.default.fetch(`${ORIGIN}/api/storefronts/v4/balance/2`, {
|
||||
@@ -1784,7 +1794,7 @@ describe('econ endpoints', () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/storefronts/v2/buyItem`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer('325', ['gameClient', 'developer'])),
|
||||
...(await bearer('325', ['gameClient'], undefined, true)),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
@@ -1806,7 +1816,7 @@ describe('econ endpoints', () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/storefronts/v2/buyItem`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer('326', ['gameClient', 'developer'])),
|
||||
...(await bearer('326', ['gameClient'], undefined, true)),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
@@ -1827,10 +1837,10 @@ describe('econ endpoints', () => {
|
||||
})
|
||||
|
||||
test('POST /api/storefronts/v2/buyItem 409s a subscriber below the discount band', async () => {
|
||||
const res = await buy2263(await bearer('323', ['gameClient', 'developer']), 84)
|
||||
const res = await buy2263(await bearer('323', ['gameClient'], undefined, true), 84)
|
||||
expect(res.status).toBe(409)
|
||||
// …and above it: a made-up price is a mismatch in either direction.
|
||||
const over = await buy2263(await bearer('323', ['gameClient', 'developer']), 96)
|
||||
const over = await buy2263(await bearer('323', ['gameClient'], undefined, true), 96)
|
||||
expect(over.status).toBe(409)
|
||||
})
|
||||
|
||||
@@ -2646,6 +2656,168 @@ describe('econ endpoints', () => {
|
||||
expect(await getOwnedInventionIds(env.DB, 60)).toEqual([])
|
||||
})
|
||||
|
||||
// The 2025 client posts the same purchase as a JSON body — and wants a DIFFERENT response
|
||||
// back: the v9 save envelope, and a balance bucket keyed `Platform`. The settlement is
|
||||
// shared with v2, so these pin the envelope and the money moving, not the rules v2 covers.
|
||||
const buyInventionV3 = async (sub: string, body: unknown) =>
|
||||
exports.default.fetch(`${ORIGIN}/api/storefronts/v3/buyInvention`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer(sub)), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
test('POST /api/storefronts/v3/buyInvention 401s without a token', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/storefronts/v3/buyInvention`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ InventionId: 8, RequestedPrice: 0 }),
|
||||
})
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
test('POST /api/storefronts/v3/buyInvention answers the v9 envelope, not v2’s', async () => {
|
||||
const res = await buyInventionV3('55', { InventionId: 8, RequestedPrice: 0 })
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as {
|
||||
InventionResponse: {
|
||||
Value: {
|
||||
Status: number
|
||||
Invention: Record<string, unknown>
|
||||
InventionVersion: unknown
|
||||
TagsResponse: unknown
|
||||
} | null
|
||||
Success: boolean
|
||||
Error: string | null
|
||||
error_id: string | null
|
||||
}
|
||||
BalanceUpdateResponse: {
|
||||
Balance: number
|
||||
CurrencyType: number
|
||||
Platform: number
|
||||
BalanceType?: number
|
||||
BalanceUpdates: Array<{ UpdateResponse: number; Data: { InventionId: number } }>
|
||||
}
|
||||
}
|
||||
|
||||
// The v9 SAVE envelope: `Value` under `{ Success, Error, error_id }`. `Value` is never
|
||||
// null under `Success: true` — the client dereferences `Value.Invention` unguarded.
|
||||
expect(body.InventionResponse).toMatchObject({ Success: true, Error: null, error_id: null })
|
||||
expect(body.InventionResponse.Value?.Status).toBe(0)
|
||||
expect(body.InventionResponse.Value?.Invention.InventionId).toBe(8)
|
||||
expect(body.InventionResponse.Value?.Invention.Name).toBe('Invention 8')
|
||||
// The 28-key `RRInvention`, not the stored record: the version rides nowhere here, and
|
||||
// `IsPublished` is a stored field this projection drops.
|
||||
expect(body.InventionResponse.Value?.Invention.CurrentVersion).toBeUndefined()
|
||||
expect(body.InventionResponse.Value?.Invention.IsPublished).toBeUndefined()
|
||||
expect(body.InventionResponse.Value?.Invention.LatestVersionNumber).toBe(1)
|
||||
// A buy mints no version and takes no tags — present and null, not absent.
|
||||
expect(body.InventionResponse.Value).toHaveProperty('InventionVersion', null)
|
||||
expect(body.InventionResponse.Value).toHaveProperty('TagsResponse', null)
|
||||
|
||||
// `BalanceResponseDTO`: the bucket key is `Platform`. Spelling it `BalanceType` (which is
|
||||
// what v2 sends) would be dropped by the client's decoder and default this balance into
|
||||
// bucket 0, beside the -2 the socket frames set — a phantom second balance.
|
||||
expect(body.BalanceUpdateResponse.Platform).toBe(-2)
|
||||
expect(body.BalanceUpdateResponse.BalanceType).toBeUndefined()
|
||||
// Nothing was debited, so `Balance` is the resulting total, not a change.
|
||||
expect(body.BalanceUpdateResponse.Balance).toBe(DEFAULT_STARTING_TOKENS)
|
||||
expect(body.BalanceUpdateResponse.CurrencyType).toBe(CurrencyType.RecCenterTokens)
|
||||
expect(body.BalanceUpdateResponse.BalanceUpdates[0].Data.InventionId).toBe(8)
|
||||
|
||||
expect(await getOwnedInventionIds(env.DB, 55)).toEqual([8])
|
||||
// Owning it is boolean here too — the route shares v2's settlement.
|
||||
expect((await buyInventionV3('55', { InventionId: 8, RequestedPrice: 0 })).status).toBe(409)
|
||||
})
|
||||
|
||||
test('GET v2 and POST v3 buyInvention answer the SAME buy in different envelopes', async () => {
|
||||
// The one thing that must not drift: two builds buying the same invention get the same
|
||||
// invention back, shaped for each. v2 serves the stored record under a bare status
|
||||
// envelope; v3 serves the 28-key projection under the v9 one. Don't unify them.
|
||||
const v2 = (await (await buyInvention('58', 8)).json()) as {
|
||||
InventionResponse: { Status: number; Invention: Record<string, unknown> }
|
||||
BalanceUpdateResponse: { BalanceType: number; Platform?: number }
|
||||
}
|
||||
const v3 = (await (
|
||||
await buyInventionV3('59', { InventionId: 8, RequestedPrice: 0 })
|
||||
).json()) as {
|
||||
InventionResponse: { Value: { Invention: Record<string, unknown> } | null }
|
||||
BalanceUpdateResponse: { Platform: number; BalanceType?: number }
|
||||
}
|
||||
expect(v2.InventionResponse.Invention.InventionId).toBe(8)
|
||||
expect(v3.InventionResponse.Value?.Invention.InventionId).toBe(8)
|
||||
// v2 keeps the nested version; v3's projection lifts it away entirely.
|
||||
expect(v2.InventionResponse.Invention.CurrentVersion).toBeDefined()
|
||||
expect(v3.InventionResponse.Value?.Invention.CurrentVersion).toBeUndefined()
|
||||
// The bucket is spelled differently on each, and each spells exactly one.
|
||||
expect(v2.BalanceUpdateResponse).toMatchObject({ BalanceType: -2 })
|
||||
expect(v2.BalanceUpdateResponse.Platform).toBeUndefined()
|
||||
expect(v3.BalanceUpdateResponse).toMatchObject({ Platform: -2 })
|
||||
expect(v3.BalanceUpdateResponse.BalanceType).toBeUndefined()
|
||||
})
|
||||
|
||||
test('POST /api/storefronts/v3/buyInvention pays the creator and pushes both sides', async () => {
|
||||
await drainFrames()
|
||||
// Creator 999 has already been paid by the v2 tests above, so their resulting total is
|
||||
// read rather than assumed — it is the payout ADDED to whatever they had.
|
||||
const creatorBefore = await getBalance(
|
||||
env.DB,
|
||||
999,
|
||||
CurrencyType.RecCenterTokens,
|
||||
DEFAULT_STARTING_TOKENS
|
||||
)
|
||||
const res = await buyInventionV3('56', { InventionId: 9, RequestedPrice: 250 })
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as { BalanceUpdateResponse: { Balance: number } }
|
||||
expect(body.BalanceUpdateResponse.Balance).toBe(DEFAULT_STARTING_TOKENS - 250)
|
||||
expect(
|
||||
await getBalance(env.DB, 56, CurrencyType.RecCenterTokens, DEFAULT_STARTING_TOKENS)
|
||||
).toBe(DEFAULT_STARTING_TOKENS - 250)
|
||||
expect(
|
||||
await getBalance(env.DB, 999, CurrencyType.RecCenterTokens, DEFAULT_STARTING_TOKENS)
|
||||
).toBe(creatorBefore + 250)
|
||||
expect(await getOwnedInventionIds(env.DB, 56)).toEqual([9])
|
||||
|
||||
// Same two frames as the v2 buy, each carrying its player's RESULTING total into the -2
|
||||
// bucket: the creator sold (a plain update), the buyer bought (a purchase frame).
|
||||
expect(await drainFrames()).toEqual([
|
||||
{
|
||||
accountId: 999,
|
||||
notificationType: NotificationType.StorefrontBalanceUpdate,
|
||||
payload: {
|
||||
Balance: creatorBefore + 250,
|
||||
CurrencyType: CurrencyType.RecCenterTokens,
|
||||
Platform: -2,
|
||||
},
|
||||
},
|
||||
{
|
||||
accountId: 56,
|
||||
notificationType: NotificationType.StorefrontBalancePurchase,
|
||||
payload: {
|
||||
BalanceAddType: 1400,
|
||||
Delta: -250,
|
||||
Balance: DEFAULT_STARTING_TOKENS - 250,
|
||||
Platform: -2,
|
||||
CurrencyType: CurrencyType.RecCenterTokens,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('POST /api/storefronts/v3/buyInvention rejects a stale price and a bad body', async () => {
|
||||
// A body is the only difference from v2, so the price check reads it the same way: an
|
||||
// absent RequestedPrice is 0, which does not match the 250-token invention 9.
|
||||
expect((await buyInventionV3('57', { InventionId: 9 })).status).toBe(409)
|
||||
expect((await buyInventionV3('57', { InventionId: 9, RequestedPrice: 0 })).status).toBe(409)
|
||||
// No InventionId, and no body at all.
|
||||
expect((await buyInventionV3('57', { RequestedPrice: 0 })).status).toBe(400)
|
||||
const res = await exports.default.fetch(`${ORIGIN}/api/storefronts/v3/buyInvention`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('57')), 'Content-Type': 'application/json' },
|
||||
})
|
||||
expect(res.status).toBe(400)
|
||||
expect(await getOwnedInventionIds(env.DB, 57)).toEqual([])
|
||||
})
|
||||
|
||||
test('POST /api/avatar/v2/gifts/consume opens the box the way the client sends it', async () => {
|
||||
// Buy an item for account 24, then consume the box the way the client does: on the
|
||||
// econ host, with a form body (`Id=..&UnlockedLevel=..`).
|
||||
@@ -3113,6 +3285,7 @@ describe('econ endpoints', () => {
|
||||
AvatarItemDesc: string
|
||||
ConsumableItemDesc: string
|
||||
GiftRarity: number
|
||||
GiftContext: number
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -3512,6 +3685,94 @@ describe('econ endpoints', () => {
|
||||
expect(held.map((cons) => cons.ConsumableItemDesc)).toContain(consumableBox?.ConsumableItemDesc)
|
||||
})
|
||||
|
||||
test('a giftContext naming a quest-rewards.json key pays one of that activity’s rewards', async () => {
|
||||
const request = async (body: string) =>
|
||||
exports.default.fetch(`${ORIGIN}/api/gamerewards/v1/request`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer('83')),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body,
|
||||
})
|
||||
await drainFrames()
|
||||
|
||||
// Quest_Goblin_S: forty avatar-item rewards, all at the goblin quest's S-rank context.
|
||||
const goblin = await request(
|
||||
'rewardType=PostGameActivity&Message=Quest%20complete&giftContext=Quest_Goblin_S'
|
||||
)
|
||||
expect(goblin.status).toBe(200)
|
||||
expect(await goblin.json()).toEqual([])
|
||||
const boxes = await giftBoxes('83')
|
||||
expect(boxes).toHaveLength(1)
|
||||
const box = boxes[0]
|
||||
expect(box).toMatchObject({ Xp: 5, Message: 'Quest complete', GiftContext: 4003 })
|
||||
expect(box?.AvatarItemDesc).not.toBe('')
|
||||
const row = questRewards.Quest_Goblin_S.find((r) => r.AvatarItemDesc === box?.AvatarItemDesc)
|
||||
expect(row).toBeDefined()
|
||||
expect(box?.GiftRarity).toBe(row?.GiftRarity)
|
||||
// …and the item is in the inventory, not just on the box.
|
||||
const items = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`, {
|
||||
headers: await bearer('83'),
|
||||
})
|
||||
const owned = (await items.json()) as Array<{ avatarItemDesc: string }>
|
||||
expect(owned.map((i) => i.avatarItemDesc)).toContain(box?.AvatarItemDesc)
|
||||
// The box announces the activity's context, not the generic GameRewards one.
|
||||
const frames = await drainFrames()
|
||||
expect(frames[0]?.notificationType).toBe(NotificationType.GiftPackageReceivedImmediate)
|
||||
expect(frames[0]?.payload).toMatchObject({
|
||||
GiftContext: 4003,
|
||||
AvatarItemDesc: box?.AvatarItemDesc,
|
||||
})
|
||||
|
||||
// Lasertag's single reward is 50 Laser Tag tickets: credited to the balance, no item.
|
||||
const before = await getBalance(
|
||||
env.DB,
|
||||
83,
|
||||
CurrencyType.LaserTagTickets,
|
||||
DEFAULT_STARTING_TOKENS
|
||||
)
|
||||
expect((await request('rewardType=PostGameActivity&giftContext=Lasertag')).status).toBe(200)
|
||||
expect(
|
||||
await getBalance(env.DB, 83, CurrencyType.LaserTagTickets, DEFAULT_STARTING_TOKENS)
|
||||
).toBe(before + 50)
|
||||
const ticketBox = (await giftBoxes('83'))[1]
|
||||
expect(ticketBox).toMatchObject({
|
||||
Currency: 50,
|
||||
CurrencyType: CurrencyType.LaserTagTickets,
|
||||
AvatarItemDesc: '',
|
||||
GiftContext: 9000,
|
||||
})
|
||||
const ticketFrames = await drainFrames()
|
||||
expect(ticketFrames.map((f) => f.notificationType)).toContain(
|
||||
NotificationType.StorefrontBalanceUpdate
|
||||
)
|
||||
|
||||
// An activity the table doesn't know pays the plain XP box, as before. (The LAST box:
|
||||
// the two claims above also crossed level 1, and that level-up box sits in between.)
|
||||
expect((await request('rewardType=PostGameActivity&giftContext=Bowling')).status).toBe(200)
|
||||
const plain = (await giftBoxes('83')).at(-1)
|
||||
expect(plain).toMatchObject({ Xp: 5, AvatarItemDesc: '', Currency: 0, GiftContext: 50 })
|
||||
|
||||
// A reward the player already owns is never drawn again: Dodgeball has three rows, so
|
||||
// three claims hand over all three, and a fourth — nothing left to give — pays the
|
||||
// plain XP box rather than a duplicate.
|
||||
const dodgeball = questRewards.Dodgeball.map((r) => r.AvatarItemDesc)
|
||||
const handed: string[] = []
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await env.DB.prepare(
|
||||
"DELETE FROM reward_status WHERE account_id = 83 AND gift_context = 'Dodgeball'"
|
||||
).run()
|
||||
expect((await request('rewardType=PostGameActivity&giftContext=Dodgeball')).status).toBe(200)
|
||||
const latest = (await giftBoxes('83')).findLast(
|
||||
(b) => b.GiftContext === 8000 || b.GiftContext === 50
|
||||
)
|
||||
if (i < 3) handed.push(latest?.AvatarItemDesc as string)
|
||||
else expect(latest).toMatchObject({ AvatarItemDesc: '', GiftContext: 50 })
|
||||
}
|
||||
expect(handed.toSorted()).toEqual(dodgeball.toSorted())
|
||||
})
|
||||
|
||||
test('POST /api/gamerewards/v1/request is 401 without a token, and ignores a typeless ask', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/gamerewards/v1/request`, {
|
||||
method: 'POST',
|
||||
@@ -3657,7 +3918,7 @@ describe('econ endpoints', () => {
|
||||
})
|
||||
|
||||
test('POST /api/CampusCard/v1/UpdateAndGetSubscription gives a developer a Gold year', async () => {
|
||||
const res = await getSubscription(await bearer('205', ['gameClient', 'developer']))
|
||||
const res = await getSubscription(await bearer('205', ['gameClient'], undefined, true))
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as {
|
||||
Subscription: Record<string, unknown>
|
||||
@@ -3689,7 +3950,7 @@ describe('econ endpoints', () => {
|
||||
})
|
||||
|
||||
test('POST /api/CampusCard/v1/UpdateAndGetSubscription is {} without the developer role', async () => {
|
||||
// A plain player's token: valid, but no elevated role.
|
||||
// A plain player's token: valid, no elevated role, and no `hasPlus` on the account.
|
||||
expect(await (await getSubscription(await bearer('206', ['gameClient']))).json()).toEqual({})
|
||||
// A token with no `role` claim at all.
|
||||
expect(await (await getSubscription(await bearer('206'))).json()).toEqual({})
|
||||
@@ -3699,6 +3960,56 @@ describe('econ endpoints', () => {
|
||||
expect(await anon.json()).toEqual({})
|
||||
})
|
||||
|
||||
// Plus reaches this worker as the token's `rn.plus` claim, which `auth` stamps from
|
||||
// `account.hasPlus` at login. Nothing here reads the account, so this is the whole
|
||||
// mechanism — and the reason a player who claims on the website has to sign in again.
|
||||
//
|
||||
// The token carries only `gameClient`, exactly as a game client's does.
|
||||
test('POST /api/CampusCard/v1/UpdateAndGetSubscription honours the rn.plus claim', async () => {
|
||||
const res = await getSubscription(await bearer('9208', ['gameClient'], undefined, true))
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as { Subscription: Record<string, unknown> }
|
||||
expect(body.Subscription).toMatchObject({
|
||||
SubscriptionId: 1,
|
||||
RecNetPlayerId: 9208,
|
||||
PlatformType: -1,
|
||||
Level: 0,
|
||||
Period: 1,
|
||||
IsAutoRenewing: true,
|
||||
})
|
||||
})
|
||||
|
||||
// The `developer` role used to BE the subscription, as a stand-in while nothing else
|
||||
// could confer one. Now that Plus has a real source it is one thing with one source, and
|
||||
// an elevated account is not a subscriber unless it also holds `rn.plus`. Pinned because
|
||||
// nothing else would fail if the old shortcut came back: it would silently hand Plus (and
|
||||
// the 10% discount) to every operator account.
|
||||
test('the developer role alone is not a Rec Room Plus subscription', async () => {
|
||||
const dev = await getSubscription(await bearer('9210', ['gameClient', 'developer']))
|
||||
expect(dev.status).toBe(200)
|
||||
expect(await dev.json()).toEqual({})
|
||||
|
||||
// …and it buys nothing at the subscriber price either, so the report and the buy path
|
||||
// agree. 85 is the SubscriberPrices entry for sf300's 2263; 95 is the list price.
|
||||
const discounted = await buy2263(await bearer('9211', ['gameClient', 'developer']), 85)
|
||||
expect(discounted.status).toBe(409)
|
||||
})
|
||||
|
||||
// Plus is priced, not just displayed: the same claim gates the subscriber discount band
|
||||
// on a buy. A subscriber whose client applied the discount itself and then had the
|
||||
// purchase refused as a price mismatch is exactly what one definition prevents, so the
|
||||
// CampusCard report and the buy must never disagree.
|
||||
test('an rn.plus token is charged the subscriber price', async () => {
|
||||
const res = await buy2263(await bearer('9326', ['gameClient'], undefined, true), 85)
|
||||
expect(res.status).toBe(200)
|
||||
expect(((await res.json()) as { Balance: number }).Balance).toBe(-85)
|
||||
|
||||
// The same request without the claim is refused, so the discount really comes from
|
||||
// `rn.plus` and not from the band being open to everyone.
|
||||
const plain = await buy2263(await bearer('9327', ['gameClient']), 85)
|
||||
expect(plain.status).toBe(409)
|
||||
})
|
||||
|
||||
test('unknown path returns 404', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/nope`)
|
||||
expect(res.status).toBe(404)
|
||||
@@ -3786,6 +4097,7 @@ describe('econ endpoints', () => {
|
||||
'POST /api/objectives/v1/cleargroup',
|
||||
'POST /api/objectives/v1/updateobjective',
|
||||
'POST /api/storefronts/v2/buyItem',
|
||||
'POST /api/storefronts/v3/buyInvention',
|
||||
'POST /api/ugcPurchasables/v1/items/bulk',
|
||||
'PUT /api/equipment/v1/update',
|
||||
])
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
@@ -0,0 +1,16 @@
|
||||
-- Server statistics sampled over time — one row per sample, as the presence cron
|
||||
-- writes them. Generated from packages/domain/src/stats-db.ts (STAT_SCHEMA_DDL) — keep
|
||||
-- in sync.
|
||||
--
|
||||
-- `stat_type` names what was measured (currently just `online`: the number of live
|
||||
-- `presence` rows, i.e. players online, taken right after the expired ones are swept).
|
||||
-- `value` is the measurement and `datetime` is when it was taken, as an ISO-8601 UTC
|
||||
-- string so it reads directly and sorts lexically.
|
||||
CREATE TABLE IF NOT EXISTS stat (
|
||||
stat_type TEXT NOT NULL,
|
||||
value INTEGER NOT NULL,
|
||||
datetime TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- For "the `online` series over a time range".
|
||||
CREATE INDEX IF NOT EXISTS idx_stat_type_datetime ON stat (stat_type, datetime);
|
||||
@@ -53,17 +53,21 @@ export type Env = SharedHonoEnv & {
|
||||
/** The Photon Chat application id. Optional; see {@link Env.PHOTON_REALTIME_APP_ID}. */
|
||||
PHOTON_CHAT_APP_ID?: string
|
||||
/**
|
||||
* The Tachyon voice server the client is handed as `voiceConnectionInfo` on
|
||||
* `GET /player/connection-info`, as `host:port` (e.g. `66.228.47.217:7777`).
|
||||
* Optional, and EMPTY when unset — no separate voice server. Not a secret (the
|
||||
* client receives it in the clear), so a plain var like the Photon ids.
|
||||
* The pool of Tachyon servers sessions are spread across — a COMMA-SEPARATED list of
|
||||
* `host:port` entries (e.g. `66.228.47.217:7777,66.228.47.217:7778,45.79.2.10:7777`).
|
||||
* One entry is a single server, which is the common case. Optional, and EMPTY when
|
||||
* unset — no separate voice server, and the connection info's voice fields stay empty.
|
||||
* Not a secret (the client receives it in the clear), so a plain var like the Photon
|
||||
* ids.
|
||||
*
|
||||
* A room instance is assigned one entry for its lifetime and every player in it is
|
||||
* handed that one, derived from the instance id rather than stored — see
|
||||
* `tachyonServerFor` in match.app.ts, which also explains what changing this list does
|
||||
* to sessions already running. The `voiceServerId` the client displays is GENERATED
|
||||
* from an entry's position (`tachyon-1`, `tachyon-2`, …), so the same address may be
|
||||
* listed twice to model two server slots on one box.
|
||||
*/
|
||||
TACHYON_HOST_PORT?: string
|
||||
/**
|
||||
* The id of that voice server, handed to the client as `voiceServerId` (e.g.
|
||||
* `server-1`). Optional; see {@link Env.TACHYON_HOST_PORT} — set both or neither.
|
||||
*/
|
||||
TACHYON_NAME?: string
|
||||
/**
|
||||
* The Photon region every session is pinned to — both the region named in the connection
|
||||
* info and the one stamped on every room instance, which must agree. Optional; unlike the
|
||||
|
||||
+333
-32
@@ -6,6 +6,7 @@ import {
|
||||
Accessibility,
|
||||
areFriends,
|
||||
canManageRoom,
|
||||
countOnlinePlayers,
|
||||
createRoomInstance,
|
||||
createRoomInvite,
|
||||
deleteEmptyRoomInstances,
|
||||
@@ -17,6 +18,8 @@ import {
|
||||
getExpiredPresenceInstanceIds,
|
||||
getFriendIds,
|
||||
getJoinableInstance,
|
||||
deleteRoomInvite,
|
||||
getLatestRoomInviteBetween,
|
||||
getMostActiveClubhouses,
|
||||
getOrCreateDormRoom,
|
||||
getPresence,
|
||||
@@ -35,6 +38,7 @@ import {
|
||||
MessageType,
|
||||
MOST_ACTIVE_CLUBHOUSE_LIMIT,
|
||||
recordRoomVisit,
|
||||
recordStat,
|
||||
refreshInstanceFullness,
|
||||
RoomInstanceType,
|
||||
setPresence,
|
||||
@@ -163,6 +167,66 @@ function instancePhotonRegion(env: Env): string {
|
||||
return varOr(env.PHOTON_REGION, DEFAULT_PHOTON_REGION)
|
||||
}
|
||||
|
||||
/**
|
||||
* One Tachyon server this deployment can put a session on: where the client connects
|
||||
* (`host:port`) and the cosmetic id it displays for it.
|
||||
*/
|
||||
interface TachyonServer {
|
||||
hostPort: string
|
||||
serverId: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The Tachyon servers sessions are spread across, from `TACHYON_HOST_PORT` — a
|
||||
* comma-separated list of `host:port` entries. EMPTY when the var is unset: recflare
|
||||
* runs no Tachyon server of its own, and a client handed an address that answers
|
||||
* nothing is worse off than one told there is no voice server at all.
|
||||
*
|
||||
* An entry's POSITION in the list is its identity: `voiceServerId` is generated from it
|
||||
* (`tachyon-1`, `tachyon-2`, …) rather than configured, so the same host may appear
|
||||
* twice and count as two servers — which is what one box running several server slots
|
||||
* looks like from here. The id is cosmetic (the client connects to the address and
|
||||
* never sends the id anywhere), but it is positional, so inserting an entry renames
|
||||
* every server after it.
|
||||
*/
|
||||
function tachyonPool(env: Env): TachyonServer[] {
|
||||
return varOr(env.TACHYON_HOST_PORT, '')
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry !== '')
|
||||
.map((hostPort, i) => ({ hostPort, serverId: `tachyon-${i + 1}` }))
|
||||
}
|
||||
|
||||
/** What the connection info carries when there is no Tachyon server to name. */
|
||||
const NO_TACHYON_SERVER: TachyonServer = { hostPort: '', serverId: '' }
|
||||
|
||||
/**
|
||||
* The Tachyon server a room instance runs on — the whole of the distributed selection,
|
||||
* and deliberately a pure function of the instance id rather than a stored assignment.
|
||||
*
|
||||
* The point of a server assignment is that everyone in one instance is handed the SAME
|
||||
* one: the player whose matchmake created the instance and everyone who joins it later
|
||||
* each call `GET /player/connection-info` separately, so an assignment made per REQUEST
|
||||
* (random, round-robin over a counter, least-loaded) would scatter one session across
|
||||
* the pool. Deriving it from the instance id instead makes every caller compute the same
|
||||
* answer without coordinating, needs no column to persist and no cleanup when the
|
||||
* instance is swept, and answers for instances created before this existed.
|
||||
*
|
||||
* Instance ids are sequential ({@link createRoomInstance} allocates `MAX(id) + 1`), so
|
||||
* the modulo hands successive instances to successive servers: a plain round-robin over
|
||||
* instances, which is the spread a real allocator would aim for anyway. Changing the
|
||||
* pool DOES move live instances — the list is the assignment — so add entries to the
|
||||
* end and expect a session mid-flight to be told a different server when you don't.
|
||||
*
|
||||
* `roomInstanceId` 0 means the caller resolved to no instance at all (they're in no
|
||||
* room, or named one that doesn't exist); they get no server rather than server one.
|
||||
*/
|
||||
function tachyonServerFor(env: Env, roomInstanceId: number): TachyonServer {
|
||||
const pool = tachyonPool(env)
|
||||
if (pool.length === 0 || roomInstanceId <= 0) return NO_TACHYON_SERVER
|
||||
return pool[roomInstanceId % pool.length] ?? NO_TACHYON_SERVER
|
||||
}
|
||||
|
||||
/**
|
||||
* Networking feature flags the client reads off its connection info. Verbatim from
|
||||
* the reference server — the client changes how it replicates based on these, so they
|
||||
@@ -595,6 +659,63 @@ function crossBuildRefusal(
|
||||
return instanceVersion > callerVersion ? MatchmakingErrorCode.UpdateRequired : NO_SUCH_ROOM
|
||||
}
|
||||
|
||||
/**
|
||||
* The 2023 client build, as its token's `rn.ver` date stamps it (`20230414`, or a point
|
||||
* release of it). See {@link persistenceVersionRefusal}.
|
||||
*/
|
||||
const BUILD_2023 = 20230414
|
||||
|
||||
/**
|
||||
* The first scene persistence version the 2023 client cannot load. A room whose published
|
||||
* scene was saved at this version or later was built on a newer client; the old one fails
|
||||
* to deserialize it.
|
||||
*/
|
||||
const MIN_UNLOADABLE_PERSISTENCE_VERSION_2023 = 227
|
||||
|
||||
/**
|
||||
* The persistence version of the scene a subroom LOADS — the published `CurrentSave`'s,
|
||||
* the same save {@link subRoomDataBlob} serves, falling back to the flat legacy field.
|
||||
* `null` when nothing recorded one (a fresh subroom, or one saved before the field
|
||||
* existed): unknown is not "new".
|
||||
*/
|
||||
function subRoomPersistenceVersion(sub: Record<string, unknown> | undefined): number | null {
|
||||
const save = sub?.CurrentSave
|
||||
if (save && typeof save === 'object') {
|
||||
const v = (save as Record<string, unknown>).PersistenceVersion
|
||||
if (typeof v === 'number') return v
|
||||
}
|
||||
return typeof sub?.PersistenceVersion === 'number' ? sub.PersistenceVersion : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a player on `callerVersion` may enter `room` at all — `null` when they may,
|
||||
* otherwise the code to refuse with.
|
||||
*
|
||||
* A caller on the 2023 build ({@link BUILD_2023}) is refused any room with a subroom whose
|
||||
* published scene is at persistence version {@link MIN_UNLOADABLE_PERSISTENCE_VERSION_2023}
|
||||
* or above: that scene was saved by a newer client and the 2023 one can't load it, so
|
||||
* the honest answer is `UpdateRequired` — "your client can't go there" — rather than
|
||||
* handing out an instance that never finishes loading. The WHOLE room is gated, not just
|
||||
* the requested subroom, since the client walks between subrooms without re-matchmaking.
|
||||
*
|
||||
* Every other build, and a token that names none, passes: the gate is about one known
|
||||
* client, not a general ordering.
|
||||
*/
|
||||
function persistenceVersionRefusal(
|
||||
room: Room,
|
||||
callerVersion: string | null
|
||||
): MatchmakingErrorCode | null {
|
||||
if (buildNumber(callerVersion) !== BUILD_2023) return null
|
||||
const subRooms = (Array.isArray(room.SubRooms) ? room.SubRooms : []) as Array<
|
||||
Record<string, unknown>
|
||||
>
|
||||
const tooNew = subRooms.some((sub) => {
|
||||
const v = subRoomPersistenceVersion(sub)
|
||||
return v !== null && v >= MIN_UNLOADABLE_PERSISTENCE_VERSION_2023
|
||||
})
|
||||
return tooNew ? MatchmakingErrorCode.UpdateRequired : null
|
||||
}
|
||||
|
||||
/**
|
||||
* "This event isn't open to you" — the refusal on a private event the caller wasn't
|
||||
* invited to. Told plainly rather than hidden behind the opaque NoSuchRoom: a player
|
||||
@@ -668,14 +789,6 @@ async function gameInviteType(c: Context<App>): Promise<MessageType> {
|
||||
*/
|
||||
const GAME_INVITE_V2_INVITE_MODE = InviteMode.PlayTogether
|
||||
|
||||
/**
|
||||
* The placeholder `InviteId` on a v2 invite that has no `room_invite` row behind it — the
|
||||
* party fan-out, which invites without recording anything. What the client does with the
|
||||
* field isn't known yet; `POST /invite` passes the real row id (see {@link RoomInvite}),
|
||||
* which is the obvious candidate, and this stands in where there is no row to name.
|
||||
*/
|
||||
const UNKNOWN_INVITE_ID = 0
|
||||
|
||||
/** What an invite points at, in the shape both message versions need to describe it. */
|
||||
type GameInviteTarget = {
|
||||
/** The raw roomInstanceId string — the WHOLE `Data` of a v1 invite. */
|
||||
@@ -684,7 +797,7 @@ type GameInviteTarget = {
|
||||
roomId: number | null
|
||||
/** The instance's `^`-prefixed wire name, `''` when the instance didn't resolve. */
|
||||
name: string
|
||||
/** The `room_invite` row this came from, or {@link UNKNOWN_INVITE_ID}. */
|
||||
/** The id of the `room_invite` row this came from — what the invitee redeems. */
|
||||
inviteId: number
|
||||
}
|
||||
|
||||
@@ -1064,6 +1177,12 @@ async function readMatchmakeBody(
|
||||
* sends, pointing at this instance, so a party matchmake pulls the whole party along. The
|
||||
* leader is skipped (already in). Best-effort per member (sendGameInvite swallows its own
|
||||
* failures), and never blocks the matchmake beyond the sends themselves.
|
||||
*
|
||||
* Each member's invite is RECORDED, exactly as `POST /invite` records one, because the row
|
||||
* is what the member redeems the frame against: the 2025 client joins off a party invite
|
||||
* through `/matchmake/invite/{InviteId}` or `/matchmake/v2/player/{leaderId}`, and both
|
||||
* resolve a `room_invite` row. A fan-out that only pushed the frame minted no row, so every
|
||||
* party invite read as expired the moment it arrived while a manual `POST /invite` worked.
|
||||
*/
|
||||
async function inviteParty(
|
||||
c: Context<App>,
|
||||
@@ -1071,21 +1190,33 @@ async function inviteParty(
|
||||
playerIds: number[],
|
||||
instance: RoomInstance
|
||||
): Promise<void> {
|
||||
// The leader's own instance, which every member is being pulled into. Nothing records
|
||||
// a `room_invite` row on this path, so a v2 invite has no real id to name.
|
||||
const target: GameInviteTarget = {
|
||||
instanceId: String(instance.roomInstanceId),
|
||||
roomId: instance.roomId,
|
||||
name: instance.name,
|
||||
inviteId: UNKNOWN_INVITE_ID,
|
||||
}
|
||||
// One read of the leader's token for the whole party — every member gets the same
|
||||
// message, so the type can't differ between them.
|
||||
const type = await gameInviteType(c)
|
||||
await Promise.all(
|
||||
playerIds
|
||||
.filter((pid) => pid !== leaderId)
|
||||
.map((pid) => sendGameInvite(c, leaderId, pid, target, type))
|
||||
.map(async (pid) => {
|
||||
// The row before the frame, as `POST /invite` does: the frame names the row's id,
|
||||
// so an invite that couldn't be recorded has nothing to redeem and isn't sent.
|
||||
const invite = await createRoomInvite(c.env.DB, leaderId, pid, instance.roomId)
|
||||
if (invite === null) {
|
||||
logger.error('failed to record party room invite', {
|
||||
fromPlayerId: leaderId,
|
||||
toPlayerId: pid,
|
||||
roomId: instance.roomId,
|
||||
})
|
||||
return
|
||||
}
|
||||
// The leader's own instance, which every member is being pulled into.
|
||||
const target: GameInviteTarget = {
|
||||
instanceId: String(instance.roomInstanceId),
|
||||
roomId: instance.roomId,
|
||||
name: instance.name,
|
||||
inviteId: invite.RoomInviteId,
|
||||
}
|
||||
await sendGameInvite(c, leaderId, pid, target, type)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1203,6 +1334,20 @@ async function resolveRoomInstance(
|
||||
return { instance: null, errorCode: BANNED_FROM_ROOM }
|
||||
}
|
||||
|
||||
// The build this player is on, from their token. A 2023 client can't load a scene
|
||||
// saved at a newer persistence version, so it is refused the room outright (see
|
||||
// persistenceVersionRefusal) before any instance is created or reused.
|
||||
const tokenVersion = await callerVersion(c)
|
||||
const tooNew = persistenceVersionRefusal(room, tokenVersion)
|
||||
if (tooNew !== null) {
|
||||
logger.info('matchmake refused: room persistence version too new for client build', {
|
||||
roomId: f.roomId,
|
||||
ownerId,
|
||||
gameVersion: tokenVersion,
|
||||
})
|
||||
return { instance: null, errorCode: tooNew }
|
||||
}
|
||||
|
||||
// Never place the player back into the instance they're already in: the client
|
||||
// keys the room transition off a changing `roomInstanceId`, so re-matchmaking into
|
||||
// your current instance (e.g. the only public instance of a room you're already in)
|
||||
@@ -1213,10 +1358,10 @@ async function resolveRoomInstance(
|
||||
const currentInstanceId = isPrivate
|
||||
? undefined
|
||||
: (await getPresence<RoomInstance>(c.env.DB, ownerId))?.roomInstance?.roomInstanceId
|
||||
// The build this player is on, from their token. It scopes the search below and is
|
||||
// stamped on the instance when one is created, which is what keeps a session to a
|
||||
// single client version.
|
||||
const gameVersion = await callerGameVersion(c)
|
||||
// The same build, with GAME_VERSION standing in for a token that names none. It scopes
|
||||
// the search below and is stamped on the instance when one is created, which is what
|
||||
// keeps a session to a single client version.
|
||||
const gameVersion = tokenVersion ?? GAME_VERSION
|
||||
// Reuse an existing joinable public instance *of the same subroom and the same
|
||||
// build* — subrooms are separate places, so joining one must never land you in
|
||||
// another, and neither must a session running a different version of the room.
|
||||
@@ -2040,6 +2185,143 @@ const app = new Hono<App>()
|
||||
}
|
||||
)
|
||||
|
||||
// The newer client's join-by-player (`/matchmake/v2/player/{playerId}`). Same move as
|
||||
// the v1 follow above — land in the instance the target is standing in — but gated on
|
||||
// the `room_invite` table rather than friendship: the caller must hold a standing
|
||||
// invite FROM the target (the newer client's invite frame doesn't always carry a
|
||||
// redeemable `InviteId` — the party fan-out sends 0 — so it redeems by player instead
|
||||
// of by row id, and this is that path). Everything the target sent stays checkable:
|
||||
// the newest row is enough, since any live row is authorization.
|
||||
//
|
||||
// The row is consumed on a successful join: an invite authorizes one entry, and since
|
||||
// this path follows the target's LIVE presence rather than the room the invite named,
|
||||
// keeping it would leave a standing key into whatever instance they're in later.
|
||||
//
|
||||
// Like the follow and invite paths, this hands out real Photon coordinates without
|
||||
// going through resolveRoomInstance, so it carries its own ban and build checks.
|
||||
// `/matchmake/v2/` answers the PascalCase envelope via `matchmakeResult`, as the v2
|
||||
// room routes do.
|
||||
.post(
|
||||
'/matchmake/v2/player/:playerId{[0-9]+}',
|
||||
describeRoute({
|
||||
tags: ['Navigation', '2025'],
|
||||
summary: 'Join the player who invited you (v2)',
|
||||
description: [
|
||||
'Places the caller into the room instance the target player is currently in, read from',
|
||||
'the target’s stored presence. INVITEES ONLY: the caller must hold a `room_invite` row',
|
||||
'FROM the target (as `POST /invite` writes them) — the newer client redeems an invite by',
|
||||
'its sender when the frame carries no usable `RoomInviteId`. The invite is SINGLE-USE:',
|
||||
'a successful join deletes the row, so the same invite can’t be redeemed again into',
|
||||
'wherever that player goes next (a refusal leaves it standing, so a retry still works).',
|
||||
'Answers 40',
|
||||
'(RoomInviteExpired) when no invite stands (expiry deletes rows, so “never invited” and',
|
||||
'“expired” are one answer), 2 (PlayerNotOnline) when the target isn’t in a room, 17',
|
||||
'(AlreadyInTargetInstance) when the caller is already standing there, 3',
|
||||
'(InsufficientSpace) when it filled up, and 55 (BannedFromRoom) when the caller is',
|
||||
'banned from that room.',
|
||||
'',
|
||||
'2025-client route: it answers the PascalCase `ErrorCode`/`RoomInstance` envelope, as',
|
||||
'the other `/matchmake/v2/*` routes do.',
|
||||
].join(' '),
|
||||
security: AUTHED,
|
||||
requestBody: form(CorrelationIdRequest, 'The attempt’s CorrelationId'),
|
||||
parameters: [
|
||||
{
|
||||
name: 'playerId',
|
||||
in: 'path',
|
||||
required: true,
|
||||
description: 'The player to join (digits only)',
|
||||
schema: { type: 'string', pattern: '^[0-9]+$' },
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: json(
|
||||
MatchmakeV2Response,
|
||||
'The target’s instance, or a null RoomInstance with the refusal code'
|
||||
),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
|
||||
const targetId = Number.parseInt(c.req.param('playerId'), 10)
|
||||
// The gate: a standing invite from the target to the caller. No row means never
|
||||
// invited or already swept — the same answer either way, since expiry deletes
|
||||
// rows. This also refuses joining yourself: nobody holds a self-invite.
|
||||
const invite = await getLatestRoomInviteBetween(c.env.DB, targetId, id)
|
||||
if (invite === null) {
|
||||
logger.info('v2 player matchmake refused: no invite from target', { targetId, id })
|
||||
return matchmakeResult(c, MatchmakingErrorCode.RoomInviteExpired, null)
|
||||
}
|
||||
|
||||
// Where the inviter is NOW, straight off their presence row — not the invite's
|
||||
// stored RoomId, which records where they were when they sent it.
|
||||
const targetPresence = await getPresence<RoomInstance>(c.env.DB, targetId)
|
||||
const instance = targetPresence?.roomInstance ?? null
|
||||
if (!instance) {
|
||||
logger.info('v2 player matchmake refused: target is not in a room', { targetId, id })
|
||||
return matchmakeResult(c, MatchmakingErrorCode.PlayerNotOnline, null)
|
||||
}
|
||||
|
||||
// Already standing there: nothing to do, and re-entering would churn presence and
|
||||
// re-fire the friend fan-out for a move that didn't happen.
|
||||
const own = await getPresence<RoomInstance>(c.env.DB, id)
|
||||
if (own?.roomInstance?.roomInstanceId === instance.roomInstanceId) {
|
||||
return matchmakeResult(c, MatchmakingErrorCode.AlreadyInTargetInstance, null)
|
||||
}
|
||||
|
||||
// Real Photon coordinates without resolveRoomInstance, so the room's bans have to
|
||||
// be checked here — otherwise an invite is a way around one.
|
||||
if (await isPlayerBannedFromRoom(c.env.DB, instance.roomId, id)) {
|
||||
logger.info('v2 player matchmake refused: player banned from room', {
|
||||
roomId: instance.roomId,
|
||||
id,
|
||||
})
|
||||
return matchmakeResult(c, BANNED_FROM_ROOM, null)
|
||||
}
|
||||
|
||||
// Nor the build scoping a room matchmake has by construction. Compared against the
|
||||
// TARGET's presence — they're the person actually standing in there.
|
||||
const refusal = crossBuildRefusal(
|
||||
await callerGameVersion(c),
|
||||
targetPresence?.appVersion ?? GAME_VERSION
|
||||
)
|
||||
if (refusal !== null) {
|
||||
logger.info('v2 player matchmake refused: target is on another client build', {
|
||||
roomInstanceId: instance.roomInstanceId,
|
||||
targetId,
|
||||
id,
|
||||
})
|
||||
return matchmakeResult(c, refusal, null)
|
||||
}
|
||||
|
||||
// Fullness read fresh, like the invite path: joins off an invite cluster exactly
|
||||
// when a nearly-full instance is still filling. Null means a synthetic instance
|
||||
// with no row (a dorm), which has no head-count to check.
|
||||
if ((await refreshInstanceFullness(c.env.DB, instance.roomInstanceId)) === true) {
|
||||
logger.info('v2 player matchmake refused: instance is full', {
|
||||
roomInstanceId: instance.roomInstanceId,
|
||||
id,
|
||||
})
|
||||
return matchmakeResult(c, MatchmakingErrorCode.InsufficientSpace, null)
|
||||
}
|
||||
|
||||
// Same instance, same Photon room, stored as the caller's presence so their
|
||||
// heartbeat replays it and their own friend fan-out fires.
|
||||
await enterRoom(c, id, instance)
|
||||
|
||||
// The invite is spent: it was authorization for THIS join, and leaving the row
|
||||
// standing would make it a permanent key into whatever instance the target is in
|
||||
// later — this path reads their live presence, not the room the invite named.
|
||||
// Dropped only once the caller is actually in, so every refusal above (target not
|
||||
// in a room, full, banned, wrong build) leaves the invite redeemable for a retry.
|
||||
await deleteRoomInvite(c.env.DB, invite.RoomInviteId)
|
||||
return matchmakeResult(c, MatchmakingErrorCode.Success, instance)
|
||||
}
|
||||
)
|
||||
|
||||
// Accept a game invite and land in the inviter's instance
|
||||
// (`/matchmake/invite/{roomInviteId}`). The 2025 client's join button on an invite: it
|
||||
// carries the `RoomInviteId` minted by `POST /invite`, and this resolves that row to the
|
||||
@@ -2496,6 +2778,10 @@ const app = new Hono<App>()
|
||||
// reads presence and nothing else; we fall back to looking the `roomInstanceId` query
|
||||
// param up when presence has no room (it expires on a TTL, and the client sometimes
|
||||
// asks before matchmaking has landed), and to an empty string when neither resolves.
|
||||
//
|
||||
// The Tachyon server is resolved from the same instance ({@link tachyonServerFor}), so
|
||||
// the player who created the session and everyone who joins it later are all sent to
|
||||
// one server without this endpoint having to remember what it told the first caller.
|
||||
.get(
|
||||
'/player/connection-info',
|
||||
describeRoute({
|
||||
@@ -2506,9 +2792,11 @@ const app = new Hono<App>()
|
||||
'`{ success, value, error }` envelope: a freshly minted `photonAuthToken`, the',
|
||||
'Photon application ids, and the `photonRoomId` of the instance the caller is in',
|
||||
'(from their presence, falling back to the `roomInstanceId` query param). The voice',
|
||||
'fields carry the Tachyon voice server (`TACHYON_HOST_PORT`/`TACHYON_NAME`),',
|
||||
'empty when none is configured. `experiments` carries the',
|
||||
'client’s networking flags.',
|
||||
'fields name the Tachyon server that instance was assigned — one entry out of the',
|
||||
'`TACHYON_HOST_PORT` pool, chosen by instance id so every player in a session is',
|
||||
'handed the same one, with a generated `voiceServerId` (`tachyon-1`, `tachyon-2`,',
|
||||
'…). Both are empty when the pool is unset or the caller is in no instance.',
|
||||
'`experiments` carries the client’s networking flags.',
|
||||
].join(' '),
|
||||
security: AUTHED,
|
||||
parameters: [
|
||||
@@ -2532,14 +2820,22 @@ const app = new Hono<App>()
|
||||
const apps = photonApps(c.env)
|
||||
const presence = await getPresence<RoomInstance>(c.env.DB, id)
|
||||
// Presence first (it's the instance the player is actually in); the query param
|
||||
// only stands in when there's no live presence to read.
|
||||
// only stands in when there's no live presence to read. The instance id travels
|
||||
// with the Photon room because the Tachyon server is derived from it — resolving
|
||||
// one without the other would hand a joiner the right Photon room on a different
|
||||
// game server than the rest of their session.
|
||||
let roomInstanceId = presence?.roomInstance?.roomInstanceId ?? 0
|
||||
let photonRoomId = presence?.roomInstance?.photonRoomId ?? ''
|
||||
if (!photonRoomId) {
|
||||
const requested = Number.parseInt(c.req.query('roomInstanceId') ?? '', 10)
|
||||
if (!Number.isNaN(requested)) {
|
||||
photonRoomId = (await getRoomInstance(c.env.DB, requested))?.photonRoomId ?? ''
|
||||
const instance = await getRoomInstance(c.env.DB, requested)
|
||||
roomInstanceId = instance?.roomInstanceId ?? 0
|
||||
photonRoomId = instance?.photonRoomId ?? ''
|
||||
}
|
||||
}
|
||||
// The instance's server, the same one every other player in it is handed.
|
||||
const tachyon = tachyonServerFor(c.env, roomInstanceId)
|
||||
|
||||
// Identifies the player to Photon. Signed with the shared JWT secret; the token's
|
||||
// `aud` is the realtime app it's for. Nothing verifies it while Photon is
|
||||
@@ -2561,13 +2857,14 @@ const app = new Hono<App>()
|
||||
photonAuthToken,
|
||||
...apps,
|
||||
photonRoomId,
|
||||
// The Tachyon voice server, from the operator's vars — empty strings when
|
||||
// unset (no separate voice server). Empty rather than null: the client's
|
||||
// The Tachyon server this instance runs on, picked out of the operator's
|
||||
// pool by {@link tachyonServerFor} — empty strings when the pool is empty
|
||||
// or the caller is in no instance. Empty rather than null: the client's
|
||||
// decoder is likelier to accept a missing-value string than a null on a
|
||||
// string field. The presence payload's NULL_CONNECTION_INFO keeps its
|
||||
// nulls — that one never carries credentials.
|
||||
voiceConnectionInfo: varOr(c.env.TACHYON_HOST_PORT, ''),
|
||||
voiceServerId: varOr(c.env.TACHYON_NAME, ''),
|
||||
voiceConnectionInfo: tachyon.hostPort,
|
||||
voiceServerId: tachyon.serverId,
|
||||
experiments: PHOTON_EXPERIMENTS,
|
||||
},
|
||||
error: null,
|
||||
@@ -2903,10 +3200,14 @@ async function sweepExpiredPresence(env: Env): Promise<void> {
|
||||
for (const instanceId of staleInstanceIds) {
|
||||
await refreshInstanceFullness(env.DB, instanceId)
|
||||
}
|
||||
// Sample the player count into `stat` — taken after the purge, so it's the live
|
||||
// rows and not the ones that just lapsed. One row per cron run: the `online` series.
|
||||
const online = await countOnlinePlayers(env.DB)
|
||||
await recordStat(env.DB, 'online', online)
|
||||
// The tagged logger is request-scoped (its middleware never runs for a cron), so
|
||||
// log plainly here — Workers observability picks it up either way.
|
||||
console.log(
|
||||
`presence sweep: removed ${removed} expired rows, deleted ${emptyInstanceIds.length} empty instances, refreshed ${staleInstanceIds.length} instances`
|
||||
`presence sweep: removed ${removed} expired rows, deleted ${emptyInstanceIds.length} empty instances, refreshed ${staleInstanceIds.length} instances, ${online} online`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -206,8 +206,9 @@ export const ConnectionExperiments = z.object({
|
||||
* recflare; what varies per caller is `photonAuthToken` (minted for them on the spot)
|
||||
* and `photonRoomId`, the Photon room of the instance their presence says they're in
|
||||
* — the same name every other player in that instance is handed. The voice fields name
|
||||
* the Tachyon voice server (`TACHYON_HOST_PORT`/`TACHYON_NAME` vars), empty when none
|
||||
* is configured. `photonRegion` matches the one stamped
|
||||
* the Tachyon server that instance was assigned out of the `TACHYON_HOST_PORT` pool —
|
||||
* likewise the same for everyone in the session — and are empty when the pool is unset
|
||||
* or the caller is in no instance. `photonRegion` matches the one stamped
|
||||
* on every room instance, so the two can't disagree.
|
||||
*/
|
||||
export const ConnectionInfo = z.object({
|
||||
@@ -219,8 +220,10 @@ export const ConnectionInfo = z.object({
|
||||
photonRoomId: z.string().describe('The caller’s current instance; empty when they’re in none'),
|
||||
voiceConnectionInfo: z
|
||||
.string()
|
||||
.describe('The Tachyon voice server, `host:port`; empty when none is configured'),
|
||||
voiceServerId: z.string().describe('The Tachyon voice server id; empty when none is configured'),
|
||||
.describe('The instance’s Tachyon server, `host:port`; empty when none is configured'),
|
||||
voiceServerId: z
|
||||
.string()
|
||||
.describe('That server’s generated id (`tachyon-1`, …); cosmetic, empty when there is none'),
|
||||
experiments: ConnectionExperiments,
|
||||
})
|
||||
|
||||
@@ -278,10 +281,11 @@ export const NotifyDisconnectRequest = z.object({
|
||||
* matchmakes that post no body at all.
|
||||
*
|
||||
* This is the whole body of the target-less matchmakes (`/matchmake/dorm`,
|
||||
* `/matchmake/none`, `/matchmake/player/:id`, `/matchmake/instance/:id`), which is why
|
||||
* it's a schema of its own; the room matchmakes extend it. Other fields the client sends
|
||||
* (`LoginLock`, `MaxPersistenceVersion`, `VoiceServerVersion`,
|
||||
* `BypassMovementModeRestriction`) are accepted and ignored.
|
||||
* `/matchmake/none`, `/matchmake/player/:id`, `/matchmake/v2/player/:id`,
|
||||
* `/matchmake/instance/:id`), which is why it's a schema of its own; the room matchmakes
|
||||
* extend it. Other fields the client sends (`LoginLock`, `MaxPersistenceVersion`,
|
||||
* `VoiceServerVersion`, `BypassMovementModeRestriction`, `PlayerIsPartyMember`) are
|
||||
* accepted and ignored.
|
||||
*/
|
||||
export const CorrelationIdRequest = z.object({
|
||||
CorrelationId: z.string().optional().describe('Per-attempt GUID; echoed on the response'),
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
ROOM_SCHEMA_DDL,
|
||||
seedRoomWithSubRooms,
|
||||
setPresence,
|
||||
STAT_SCHEMA_DDL,
|
||||
SUBROOM_SCHEMA_DDL,
|
||||
} from '@repo/domain'
|
||||
|
||||
@@ -124,6 +125,8 @@ beforeAll(async () => {
|
||||
for (const stmt of PRESENCE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
// Room invites (owned by this worker) — POST /invite mints a row per invite.
|
||||
for (const stmt of ROOM_INVITE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
// Stats (owned by this worker) — the presence cron samples the online count into it.
|
||||
for (const stmt of STAT_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
|
||||
// Accounts table (owned by the auth worker) — dorm creation reads the username
|
||||
// to name the room. Seed the players the dorm tests authenticate as.
|
||||
@@ -1244,8 +1247,9 @@ describe('auth-gated endpoints', () => {
|
||||
// The room the client is told to join has to be the one matchmaking placed
|
||||
// them in, or they end up alone in a room of their own.
|
||||
photonRoomId: matchmaked.roomInstance.photonRoomId,
|
||||
// Empty strings, not nulls — unlike the presence payload's connection fields,
|
||||
// which stay null (they never carry credentials).
|
||||
// No TACHYON_HOST_PORT pool configured, so there is no server to name. Empty
|
||||
// strings, not nulls — unlike the presence payload's connection fields, which
|
||||
// stay null (they never carry credentials).
|
||||
voiceConnectionInfo: '',
|
||||
voiceServerId: '',
|
||||
experiments: {
|
||||
@@ -1374,6 +1378,84 @@ describe('auth-gated endpoints', () => {
|
||||
expect(body.value.photonRoomId).toBe('')
|
||||
})
|
||||
|
||||
test('the Tachyon server is assigned per room instance, not per request', async () => {
|
||||
// tachyon-1/-2 and tachyon-4/-5 are slots on one host apiece: a server id names a
|
||||
// slot, which is why it's generated from the entry's position and not from its
|
||||
// address. The blank entry and the stray spaces below are dropped — a list edited
|
||||
// by hand shouldn't hand anyone an empty address.
|
||||
const pool = [
|
||||
{ voiceConnectionInfo: '198.51.100.10:7777', voiceServerId: 'tachyon-1' },
|
||||
{ voiceConnectionInfo: '198.51.100.10:7778', voiceServerId: 'tachyon-2' },
|
||||
{ voiceConnectionInfo: '198.51.100.11:7777', voiceServerId: 'tachyon-3' },
|
||||
{ voiceConnectionInfo: '203.0.113.20:7777', voiceServerId: 'tachyon-4' },
|
||||
{ voiceConnectionInfo: '203.0.113.20:7778', voiceServerId: 'tachyon-5' },
|
||||
]
|
||||
const original = env.TACHYON_HOST_PORT
|
||||
try {
|
||||
env.TACHYON_HOST_PORT =
|
||||
'198.51.100.10:7777, 198.51.100.10:7778 ,,198.51.100.11:7777,203.0.113.20:7777,203.0.113.20:7778'
|
||||
|
||||
const voiceFor = async (player: string, roomInstanceId?: number) => {
|
||||
const res = await exports.default.fetch(
|
||||
roomInstanceId === undefined
|
||||
? `${ORIGIN}/player/connection-info`
|
||||
: `${ORIGIN}/player/connection-info?roomInstanceId=${roomInstanceId}`,
|
||||
{ headers: await bearer(player) }
|
||||
)
|
||||
const body = (await res.json()) as {
|
||||
value: { voiceConnectionInfo: string; voiceServerId: string }
|
||||
}
|
||||
return {
|
||||
voiceConnectionInfo: body.value.voiceConnectionInfo,
|
||||
voiceServerId: body.value.voiceServerId,
|
||||
}
|
||||
}
|
||||
|
||||
// The player who opened the session reads their server off their presence...
|
||||
const instance = await createRoomInstance(env.DB, {
|
||||
ownerAccountId: 970,
|
||||
roomId: 2,
|
||||
photonRoomId: 'tachyon-instance-a',
|
||||
maxCapacity: 12,
|
||||
})
|
||||
await setPresence(env.DB, {
|
||||
accountId: 970,
|
||||
roomInstance: instance,
|
||||
statusVisibility: 0,
|
||||
deviceClass: 0,
|
||||
vrMovementMode: 1,
|
||||
platform: 0,
|
||||
appVersion: GAME_VERSION,
|
||||
})
|
||||
const assigned = pool[instance.roomInstanceId % pool.length]!
|
||||
expect(await voiceFor('970')).toEqual(assigned)
|
||||
|
||||
// ...and a joiner asking by instance id, before their own presence has landed,
|
||||
// is sent to the same one. Two players in a session on two servers is the whole
|
||||
// failure this is arranged to avoid.
|
||||
expect(await voiceFor('971', instance.roomInstanceId)).toEqual(assigned)
|
||||
|
||||
// The next session opened goes to the next server along — instance ids are
|
||||
// sequential, so the pool is walked round-robin as instances are created.
|
||||
const next = await createRoomInstance(env.DB, {
|
||||
ownerAccountId: 972,
|
||||
roomId: 2,
|
||||
photonRoomId: 'tachyon-instance-b',
|
||||
maxCapacity: 12,
|
||||
})
|
||||
expect(next.roomInstanceId).toBe(instance.roomInstanceId + 1)
|
||||
const alongside = await voiceFor('972', next.roomInstanceId)
|
||||
expect(alongside).toEqual(pool[next.roomInstanceId % pool.length])
|
||||
expect(alongside.voiceServerId).not.toBe(assigned.voiceServerId)
|
||||
|
||||
// A player in no instance gets no server, pool or no pool — there is nothing for
|
||||
// them to be on the same server as, and the fields stay empty strings.
|
||||
expect(await voiceFor('973')).toEqual({ voiceConnectionInfo: '', voiceServerId: '' })
|
||||
} finally {
|
||||
env.TACHYON_HOST_PORT = original
|
||||
}
|
||||
})
|
||||
|
||||
test('re-matchmaking into your current room returns a different instance (id must change)', async () => {
|
||||
// The client keys the room transition off a changing roomInstanceId; handing back
|
||||
// the instance the player is already in hangs their join. RecCenter (cap 12) so
|
||||
@@ -1909,6 +1991,30 @@ describe('auth-gated endpoints', () => {
|
||||
expect((await getRoomInstance(env.DB, solo))?.isFull).toBe(false)
|
||||
})
|
||||
|
||||
test('records an `online` stat sample of the live presence count on each run', async () => {
|
||||
await env.DB.prepare('DELETE FROM stat').run()
|
||||
const before = (await env.DB.prepare('SELECT COUNT(*) AS n FROM presence WHERE expires_at > ?1')
|
||||
.bind(nowSeconds())
|
||||
.first<{ n: number }>())!.n
|
||||
|
||||
const ctx = createExecutionContext()
|
||||
await scheduled(createScheduledController(), env, ctx)
|
||||
await waitOnExecutionContext(ctx)
|
||||
|
||||
const rows = (
|
||||
await env.DB.prepare('SELECT stat_type, value, datetime FROM stat').all<{
|
||||
stat_type: string
|
||||
value: number
|
||||
datetime: string
|
||||
}>()
|
||||
).results
|
||||
expect(rows).toHaveLength(1)
|
||||
expect(rows[0]!.stat_type).toBe('online')
|
||||
expect(rows[0]!.value).toBe(before)
|
||||
// Stamped with the current time, as ISO-8601.
|
||||
expect(Math.abs(Date.parse(rows[0]!.datetime) - Date.now())).toBeLessThan(10_000)
|
||||
})
|
||||
|
||||
// Age an instance past EMPTY_INSTANCE_GRACE_SECONDS by backdating its `createdAt`
|
||||
// (the generated `created_at` column follows the blob), so the empty-instance sweep
|
||||
// can be exercised without waiting out the grace window.
|
||||
@@ -2444,9 +2550,7 @@ describe('auth-gated endpoints', () => {
|
||||
|
||||
// The one frame an invite from a client on `version` pushes, with the RoomInviteId
|
||||
// the call answered — a v2 invite names it in its Data.
|
||||
const inviteFrom = async (
|
||||
version?: string
|
||||
): Promise<{ frame: Sent; roomInviteId: number }> => {
|
||||
const inviteFrom = async (version?: string): Promise<{ frame: Sent; roomInviteId: number }> => {
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
const res = await exports.default.fetch(`${ORIGIN}/invite`, {
|
||||
method: 'POST',
|
||||
@@ -2810,6 +2914,14 @@ describe('auth-gated endpoints', () => {
|
||||
RoomId: 2,
|
||||
})
|
||||
|
||||
// The fan-out RECORDS each invite, like POST /invite: the row is what the invitee
|
||||
// redeems the frame against, so a frame without one is expired on arrival.
|
||||
const row = await env.DB.prepare(
|
||||
'SELECT room_invite_id, room_id FROM room_invite WHERE from_player_id = 9850 AND to_player_id = 153'
|
||||
).first<{ room_invite_id: number; room_id: number }>()
|
||||
expect(row).not.toBeNull()
|
||||
expect(row?.room_id).toBe(2)
|
||||
|
||||
// Multiple ids (repeated fields, not comma-separated), de-duplicated, and the leader
|
||||
// themselves is skipped.
|
||||
await reset()
|
||||
@@ -2825,6 +2937,58 @@ describe('auth-gated endpoints', () => {
|
||||
expect(await sent()).toEqual([])
|
||||
})
|
||||
|
||||
test('a v2 party matchmake mints invites the members can actually redeem', async () => {
|
||||
// The reported bug: /matchmake/v2/room/:id fanned the party out as frames with no
|
||||
// `room_invite` row behind them, so the member's join answered 40 (RoomInviteExpired)
|
||||
// while a manual POST /invite worked.
|
||||
type Sent = { playerId: number; data: { Type: number; Data: string } }
|
||||
const hub = () => env.RECFLARE_NOTIFICATIONS_HUB.getByName('global')
|
||||
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||
|
||||
const res = await exports.default.fetch(`${ORIGIN}/matchmake/v2/room/2`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer('9860', '20250718.01')),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
AdditionalPlayerIds: [9861],
|
||||
CorrelationId: '3c60e657-21c4-46be-815c-57ee51add506',
|
||||
JoinMode: 2,
|
||||
InviteMode: 20,
|
||||
}),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
const leader = (await res.json()) as { RoomInstance: { RoomInstanceId: number } }
|
||||
|
||||
// The party member's frame is a v2 invite naming a REAL row id.
|
||||
const frames = (await (await hub().fetch('http://do/all')).json()) as Sent[]
|
||||
const invite = frames.find((f) => f.playerId === 9861)
|
||||
expect(invite?.data.Type).toBe(6) // MessageType.GameInviteV2
|
||||
const { InviteId } = JSON.parse(invite?.data.Data ?? '{}') as { InviteId: number }
|
||||
expect(InviteId).toBeGreaterThan(0)
|
||||
|
||||
// Redeeming it puts the member in the leader's instance rather than answering 40.
|
||||
const joined = (await (
|
||||
await exports.default.fetch(`${ORIGIN}/matchmake/invite/${InviteId}`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('9861', '20250718.01')) },
|
||||
})
|
||||
).json()) as { ErrorCode: number; RoomInstance: { RoomInstanceId: number } | null }
|
||||
expect(joined.ErrorCode).toBe(0)
|
||||
expect(joined.RoomInstance?.RoomInstanceId).toBe(leader.RoomInstance.RoomInstanceId)
|
||||
|
||||
// The by-sender redemption the newer client falls back to works off the same row.
|
||||
await env.DB.prepare('DELETE FROM presence WHERE account_id = 9861').run()
|
||||
const bySender = (await (
|
||||
await exports.default.fetch(`${ORIGIN}/matchmake/v2/player/9860`, {
|
||||
method: 'POST',
|
||||
headers: { ...(await bearer('9861', '20250718.01')) },
|
||||
})
|
||||
).json()) as { ErrorCode: number }
|
||||
expect(bySender.ErrorCode).toBe(0)
|
||||
})
|
||||
|
||||
test('POST /matchmake/invite/:id lands the invitee in the inviter’s instance', async () => {
|
||||
// 8801 invites 8802. The invite row is what POST /invite answers with.
|
||||
const instance = await createRoomInstance(env.DB, {
|
||||
@@ -2933,6 +3097,146 @@ describe('auth-gated endpoints', () => {
|
||||
).toBe(401)
|
||||
})
|
||||
|
||||
test('POST /matchmake/v2/player/:id joins the inviter, invite row required', async () => {
|
||||
// 8811 stands in an instance and invites 8812 (writing the room_invite row).
|
||||
const instance = await createRoomInstance(env.DB, {
|
||||
ownerAccountId: 8811,
|
||||
roomId: 2,
|
||||
subRoomId: 2,
|
||||
photonRoomId: crypto.randomUUID(),
|
||||
name: '^RecCenter',
|
||||
maxCapacity: 12,
|
||||
})
|
||||
await setPresence(env.DB, {
|
||||
accountId: 8811,
|
||||
roomInstance: instance,
|
||||
statusVisibility: 0,
|
||||
deviceClass: 0,
|
||||
vrMovementMode: 1,
|
||||
platform: 0,
|
||||
appVersion: GAME_VERSION,
|
||||
})
|
||||
|
||||
const join = async (targetId: number, sub: string) =>
|
||||
exports.default.fetch(`${ORIGIN}/matchmake/v2/player/${targetId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer(sub)),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'BypassMovementModeRestriction=False&LoginLock=40bacd8f-7c60-4d49-93f9-462b096602de&VoiceServerVersion=gameserver-2&CorrelationId=82c12c19-a3fc-4734-9abc-e912aeb1f351&MaxPersistenceVersion=227&PlayerIsPartyMember=False',
|
||||
})
|
||||
|
||||
// No invite from the target yet → 40, and nothing about their state leaks.
|
||||
expect(await (await join(8811, '8812')).json()).toMatchObject({
|
||||
ErrorCode: 40,
|
||||
RoomInstance: null,
|
||||
})
|
||||
|
||||
await exports.default.fetch(`${ORIGIN}/invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer('8811')),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `playerId=8812&roomInstanceId=${instance.roomInstanceId}`,
|
||||
})
|
||||
|
||||
// An invite from a DIFFERENT player doesn't authorize this target: 8813 holds no
|
||||
// invite from 8811.
|
||||
expect(await (await join(8811, '8813')).json()).toMatchObject({
|
||||
ErrorCode: 40,
|
||||
RoomInstance: null,
|
||||
})
|
||||
|
||||
// The invitee lands in the inviter's instance, in the PascalCase v2 envelope with
|
||||
// the CorrelationId echoed back.
|
||||
const ok = await join(8811, '8812')
|
||||
expect(ok.status).toBe(200)
|
||||
const okBody = (await ok.json()) as Record<string, unknown>
|
||||
expect(okBody).toMatchObject({
|
||||
ErrorCode: 0,
|
||||
CorrelationId: '82c12c19-a3fc-4734-9abc-e912aeb1f351',
|
||||
RoomInstance: {
|
||||
RoomInstanceId: instance.roomInstanceId,
|
||||
RoomId: 2,
|
||||
Name: '^RecCenter',
|
||||
MatchmakingPolicy: 0,
|
||||
},
|
||||
})
|
||||
// The exact wire shape, confirmed against the live client: the three-key envelope
|
||||
// and the 15-key v2 instance, nothing extra (no Photon coordinates, no DataBlob).
|
||||
expect(Object.keys(okBody).sort()).toEqual(['CorrelationId', 'ErrorCode', 'RoomInstance'])
|
||||
expect(Object.keys(okBody.RoomInstance as object).sort()).toEqual(
|
||||
[
|
||||
'RoomInstanceId',
|
||||
'RoomId',
|
||||
'SubRoomId',
|
||||
'Location',
|
||||
'EventId',
|
||||
'ClubId',
|
||||
'RoomCode',
|
||||
'Name',
|
||||
'MaxCapacity',
|
||||
'IsFull',
|
||||
'IsPrivate',
|
||||
'IsInProgress',
|
||||
'EncryptVoiceChat',
|
||||
'RoomInstanceType',
|
||||
'MatchmakingPolicy',
|
||||
].sort()
|
||||
)
|
||||
|
||||
// The invite was spent by that join: the row is gone, so the same call now reads as
|
||||
// "no invite" (40) rather than authorizing a second entry off the same invite.
|
||||
expect(
|
||||
await env.DB.prepare(
|
||||
'SELECT COUNT(*) AS n FROM room_invite WHERE from_player_id = 8811 AND to_player_id = 8812'
|
||||
).first<{ n: number }>()
|
||||
).toMatchObject({ n: 0 })
|
||||
expect(await (await join(8811, '8812')).json()).toMatchObject({
|
||||
ErrorCode: 40,
|
||||
RoomInstance: null,
|
||||
})
|
||||
|
||||
// With a fresh invite, standing there already is 17, not a second join — and a
|
||||
// refusal leaves that invite standing, which the PlayerNotOnline case below redeems.
|
||||
await exports.default.fetch(`${ORIGIN}/invite`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer('8811')),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `playerId=8812&roomInstanceId=${instance.roomInstanceId}`,
|
||||
})
|
||||
expect(await (await join(8811, '8812')).json()).toMatchObject({
|
||||
ErrorCode: 17,
|
||||
RoomInstance: null,
|
||||
})
|
||||
|
||||
// The inviter walking out leaves nothing to join: 2, PlayerNotOnline. (The invitee
|
||||
// is moved out first so the AlreadyIn check doesn't answer ahead of it.)
|
||||
await setPresence(env.DB, {
|
||||
accountId: 8812,
|
||||
roomInstance: null,
|
||||
statusVisibility: 0,
|
||||
deviceClass: 0,
|
||||
vrMovementMode: 1,
|
||||
platform: 0,
|
||||
appVersion: GAME_VERSION,
|
||||
})
|
||||
await env.DB.prepare('DELETE FROM presence WHERE account_id = 8811').run()
|
||||
expect(await (await join(8811, '8812')).json()).toMatchObject({
|
||||
ErrorCode: 2,
|
||||
RoomInstance: null,
|
||||
})
|
||||
|
||||
// Unauthenticated is a 401, not a refusal code.
|
||||
expect(
|
||||
(await exports.default.fetch(`${ORIGIN}/matchmake/v2/player/8811`, { method: 'POST' })).status
|
||||
).toBe(401)
|
||||
})
|
||||
|
||||
test('GET /openapi.json documents every route', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/openapi.json`)
|
||||
expect(res.status).toBe(200)
|
||||
@@ -2973,6 +3277,7 @@ describe('auth-gated endpoints', () => {
|
||||
'POST /matchmake/player/{playerId}',
|
||||
'POST /matchmake/room/{roomId}',
|
||||
'POST /matchmake/room/{roomId}/{subRoomId}',
|
||||
'POST /matchmake/v2/player/{playerId}',
|
||||
'POST /matchmake/v2/room/{roomId}',
|
||||
'POST /matchmake/v2/room/{roomId}/{subRoomId}',
|
||||
'POST /player/exclusivelogin',
|
||||
@@ -3086,6 +3391,85 @@ describe('account bans', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// A 2023 client (`rn.ver` 20230414) can't load a scene saved at persistence version 227 or
|
||||
// later, so any room with such a subroom refuses it with UpdateRequired. Every other build
|
||||
// gets in as usual.
|
||||
describe('persistence version gate for the 2023 client', () => {
|
||||
const matchmake = async (roomId: number, sub: string, version?: string) => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/matchmake/room/${roomId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer(sub, version)),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({ JoinMode: '0' }).toString(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
return (await res.json()) as {
|
||||
errorCode: number
|
||||
result: number
|
||||
roomInstance: { roomId: number } | null
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
// Published scene at 227 — the first version the 2023 client can't load.
|
||||
await seedRoomWithSubRooms(env.DB, {
|
||||
RoomId: 7227,
|
||||
Name: 'NewFormatRoom',
|
||||
IsDorm: false,
|
||||
Accessibility: 1,
|
||||
CreatorAccountId: 7300,
|
||||
SubRooms: [
|
||||
{ SubRoomId: 7227, UnitySceneId: RECCENTER_SCENE, MaxPlayers: 10 },
|
||||
{
|
||||
SubRoomId: 7228,
|
||||
UnitySceneId: SECOND_SUBROOM_SCENE,
|
||||
MaxPlayers: 10,
|
||||
CurrentSave: { DataBlob: 'new.room', PersistenceVersion: 227 },
|
||||
},
|
||||
],
|
||||
} as unknown as Record<string, unknown>)
|
||||
// Published at 226 — still loadable.
|
||||
await seedRoomWithSubRooms(env.DB, {
|
||||
RoomId: 7226,
|
||||
Name: 'OldFormatRoom',
|
||||
IsDorm: false,
|
||||
Accessibility: 1,
|
||||
CreatorAccountId: 7300,
|
||||
SubRooms: [
|
||||
{
|
||||
SubRoomId: 7226,
|
||||
UnitySceneId: RECCENTER_SCENE,
|
||||
MaxPlayers: 10,
|
||||
CurrentSave: { DataBlob: 'old.room', PersistenceVersion: 226 },
|
||||
},
|
||||
],
|
||||
} as unknown as Record<string, unknown>)
|
||||
})
|
||||
|
||||
test('the 2023 build is refused a room with a subroom at 227+', async () => {
|
||||
const res = await matchmake(7227, '7301', '20230414')
|
||||
expect(res.errorCode).toBe(16) // UpdateRequired
|
||||
expect(res.result).toBe(16)
|
||||
expect(res.roomInstance).toBeNull()
|
||||
|
||||
// A point release of the same build is the same client.
|
||||
expect((await matchmake(7227, '7302', '20230414.02')).errorCode).toBe(16)
|
||||
})
|
||||
|
||||
test('the 2023 build still enters a room saved below 227', async () => {
|
||||
const res = await matchmake(7226, '7303', '20230414')
|
||||
expect(res.errorCode).toBe(0)
|
||||
expect(res.roomInstance?.roomId).toBe(7226)
|
||||
})
|
||||
|
||||
test('newer builds and unversioned tokens are not gated', async () => {
|
||||
expect((await matchmake(7227, '7304', '20250718.01')).errorCode).toBe(0)
|
||||
expect((await matchmake(7227, '7305')).errorCode).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
// The ban follows the player past the account it was written on: a new account sharing a
|
||||
// proven platform identity or an IP with a banned one is refused the same way. See
|
||||
// bans-db.ts in the api worker for the arms and the BAN_EVASION_MATCH knob.
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
// The operator's knobs — the room substitutions (ROOM_REDIRECTS), the Photon app ids
|
||||
// and region (PHOTON_REALTIME_APP_ID, PHOTON_VOICE_APP_ID, PHOTON_CHAT_APP_ID,
|
||||
// PHOTON_REGION), and the Tachyon voice server (TACHYON_HOST_PORT, TACHYON_NAME) —
|
||||
// PHOTON_REGION), and the Tachyon server pool (TACHYON_HOST_PORT) —
|
||||
// are deliberately NOT set here. They're injected at deploy time from
|
||||
// the gitignored .env (RECFLARE_<VAR>, see .env.example), so swapping a room out or
|
||||
// pointing at your own Photon apps never means editing a versioned file. Unset — the
|
||||
|
||||
@@ -448,6 +448,28 @@ export class NotificationsHub extends DurableObject<Env> {
|
||||
return { sent: this.broadcastToConnected(payload) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The targeted form of {@link coachMessageAll}: the same `MessageReceived` frame from
|
||||
* the Coach account (player 1), addressed to one player with a `ToPlayerId` — the shape
|
||||
* `api`'s player-to-player send uses.
|
||||
*
|
||||
* Unlike the broadcast this one QUEUES when the recipient is offline (see
|
||||
* {@link notifyPlayer}). The broadcast is online-only because it has no recipient to
|
||||
* hold anything for; a message written to a named player is worth keeping until they
|
||||
* next connect.
|
||||
*/
|
||||
async coachMessage(
|
||||
playerId: number,
|
||||
content: string
|
||||
): Promise<{ delivered: number; queued: boolean }> {
|
||||
return this.notifyPlayer(playerId, NotificationType.MessageReceived, {
|
||||
FromPlayerId: COACH_PLAYER_ID,
|
||||
ToPlayerId: playerId,
|
||||
Type: COACH_MESSAGE_TYPE,
|
||||
Data: content,
|
||||
})
|
||||
}
|
||||
|
||||
/** Broadcast a notification to every connected (handshaken) client. */
|
||||
async broadcast(
|
||||
notificationType: string | number,
|
||||
|
||||
@@ -176,6 +176,25 @@ const app = new Hono<App>()
|
||||
return c.json({ success: true, ...result })
|
||||
})
|
||||
|
||||
// The targeted form of the broadcast above: one coach message to ONE player. Queued
|
||||
// by the hub when they're offline (unlike coach-message-all, which reaches only
|
||||
// whoever is connected), so this arrives either way.
|
||||
.post('/internal/coach-message', async (c) => {
|
||||
const body = await c.req
|
||||
.json<{ playerId?: number; messageContent?: string }>()
|
||||
.catch(() => null)
|
||||
const content = typeof body?.messageContent === 'string' ? body.messageContent.trim() : ''
|
||||
if (typeof body?.playerId !== 'number' || !Number.isInteger(body.playerId)) {
|
||||
return c.json({ error: 'playerId is required' }, 400)
|
||||
}
|
||||
if (content === '') return c.json({ error: 'messageContent is required' }, 400)
|
||||
const result = await c.env.RECFLARE_NOTIFICATIONS_HUB.getByName(HUB_INSTANCE).coachMessage(
|
||||
body.playerId,
|
||||
content
|
||||
)
|
||||
return c.json({ success: true, ...result })
|
||||
})
|
||||
|
||||
// Read-only view of the hub's routing state, for working out why a notification
|
||||
// didn't arrive: which connections are live, which players each one receives for,
|
||||
// and what's queued for a player who wasn't reachable.
|
||||
|
||||
@@ -65,7 +65,12 @@ interface HubRecord {
|
||||
async function connect(
|
||||
id: string,
|
||||
opts: { headers?: Record<string, string>; query?: string } = {}
|
||||
): Promise<{ ws: WebSocket; waitFor: (pred: (r: HubRecord) => boolean) => Promise<HubRecord> }> {
|
||||
): Promise<{
|
||||
ws: WebSocket
|
||||
waitFor: (pred: (r: HubRecord) => boolean) => Promise<HubRecord>
|
||||
/** Everything this socket has received so far — for asserting something did NOT arrive. */
|
||||
records: HubRecord[]
|
||||
}> {
|
||||
// The hub only accepts identified connections, so default to a token; pass explicit
|
||||
// `headers` (`{}` for none) where the test is about who is connecting.
|
||||
const auth = opts.headers ?? (await bearer(String(DEFAULT_CONNECT_PLAYER), ['gameClient']))
|
||||
@@ -113,7 +118,7 @@ async function connect(
|
||||
ws.send(`{"protocol":"json","version":1}${RS}`)
|
||||
await waitFor((r) => r.type === 1 && r.target === 'OnConnect')
|
||||
|
||||
return { ws, waitFor }
|
||||
return { ws, waitFor, records }
|
||||
}
|
||||
|
||||
const send = (ws: WebSocket, msg: HubRecord) => ws.send(JSON.stringify(msg) + RS)
|
||||
@@ -337,6 +342,72 @@ describe('notification delivery', () => {
|
||||
b.ws.close()
|
||||
})
|
||||
|
||||
test('coach-message reaches only the named player, addressed to them', async () => {
|
||||
const playerId = 9010
|
||||
const target = await connect('coach-one', {
|
||||
headers: await bearer(String(playerId), ['gameClient']),
|
||||
})
|
||||
const bystander = await connect('coach-one-bystander')
|
||||
send(target.ws, {
|
||||
type: 1,
|
||||
invocationId: 's',
|
||||
target: 'SubscribeToPlayers',
|
||||
arguments: [{ playerIds: [playerId] }],
|
||||
})
|
||||
await target.waitFor((r) => r.type === 3 && r.invocationId === 's')
|
||||
|
||||
const res = await post('/internal/coach-message', { playerId, messageContent: 'just you' })
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toMatchObject({ delivered: 1, queued: false })
|
||||
|
||||
const note = await target.waitFor((r) => r.type === 1 && r.target === 'Notification')
|
||||
const payload = JSON.parse((note.arguments as string[])[0]) as {
|
||||
Id: string
|
||||
Msg: Record<string, unknown>
|
||||
}
|
||||
expect(payload.Id).toBe('2') // MessageReceived
|
||||
// Same Coach frame as the broadcast, plus the recipient the broadcast can't name.
|
||||
expect(payload.Msg).toMatchObject({
|
||||
FromPlayerId: 1,
|
||||
ToPlayerId: playerId,
|
||||
Type: 100,
|
||||
Data: 'just you',
|
||||
})
|
||||
|
||||
// The point of the targeted send: nobody else's socket sees it. The bystander is
|
||||
// subscribed to nothing, and a broadcast would have reached it regardless.
|
||||
expect(bystander.records.some((r) => r.target === 'Notification')).toBe(false)
|
||||
|
||||
target.ws.close()
|
||||
bystander.ws.close()
|
||||
})
|
||||
|
||||
test('coach-message queues for a player who is offline', async () => {
|
||||
const playerId = 9011
|
||||
const res = await post('/internal/coach-message', {
|
||||
playerId,
|
||||
messageContent: 'catch you later',
|
||||
})
|
||||
expect(await res.json()).toMatchObject({ delivered: 0, queued: true })
|
||||
|
||||
// Unlike the broadcast, it survives until they connect.
|
||||
const { ws, waitFor } = await connect('coach-one-late')
|
||||
send(ws, { type: 1, target: 'SubscribeToPlayers', arguments: [{ playerIds: [playerId] }] })
|
||||
const note = await waitFor((r) => r.type === 1 && r.target === 'Notification')
|
||||
expect(
|
||||
(JSON.parse((note.arguments as string[])[0]) as { Msg: Record<string, unknown> }).Msg
|
||||
).toMatchObject({ FromPlayerId: 1, ToPlayerId: playerId, Data: 'catch you later' })
|
||||
ws.close()
|
||||
})
|
||||
|
||||
test('coach-message 400s without a player or a message', async () => {
|
||||
expect((await post('/internal/coach-message', { messageContent: 'nobody' })).status).toBe(400)
|
||||
expect((await post('/internal/coach-message', { playerId: 9012 })).status).toBe(400)
|
||||
expect(
|
||||
(await post('/internal/coach-message', { playerId: 9012, messageContent: ' ' })).status
|
||||
).toBe(400)
|
||||
})
|
||||
|
||||
test('coach-message-all 400s on an empty message', async () => {
|
||||
const res = await post('/internal/coach-message-all', { messageContent: ' ' })
|
||||
expect(res.status).toBe(400)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Per-room leaderboard definitions. Generated from packages/domain/src/rooms-db.ts
|
||||
-- (ROOM_SCHEMA_DDL) — keep in sync.
|
||||
--
|
||||
-- One row per (room, leaderboard). `leaderboard_id` is the client's slot number — small
|
||||
-- ordinals (1, 2, 3…), unique only within the room, which is why the pair is the primary
|
||||
-- key rather than the id alone. Re-posting a slot (POST /rooms/:id/leaderboards/:lid)
|
||||
-- reconfigures it in place; DELETE on the same path removes it.
|
||||
--
|
||||
-- `sort_ascending` stores the client's `sortAscending=True/False` as 1/0; `stat_format`
|
||||
-- is the client's `statFormat` int, echoed back as stored.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room_leaderboard (
|
||||
room_id INTEGER NOT NULL,
|
||||
leaderboard_id INTEGER NOT NULL,
|
||||
leaderboard_title TEXT NOT NULL,
|
||||
stat_format INTEGER NOT NULL DEFAULT 0,
|
||||
sort_ascending INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (room_id, leaderboard_id)
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Retire `FriendlyName` from the room blob.
|
||||
--
|
||||
-- The rename route (PUT /rooms/:id/name) briefly wrote `FriendlyName` alongside `Name`,
|
||||
-- and every read defaulted it to `Name`. Neither exists any more: this server serves no
|
||||
-- display name apart from the unique `Name`, and a stored value would otherwise survive in
|
||||
-- the blob forever (`json_set` on rename is gone, so nothing would ever update it again).
|
||||
--
|
||||
-- Strip the key from every room that carries one. `json_remove` on a blob without the key
|
||||
-- is a no-op, so the WHERE only spares the rows that need no rewrite.
|
||||
UPDATE room
|
||||
SET data = json_remove(data, '$.FriendlyName')
|
||||
WHERE json_type(data, '$.FriendlyName') IS NOT NULL;
|
||||
@@ -0,0 +1,45 @@
|
||||
-- Room roles as their own table, mirroring 0013's move of tags into `room_tag`. One row
|
||||
-- per (room, account). Generated from packages/domain/src/rooms-db.ts (ROOM_SCHEMA_DDL) —
|
||||
-- keep in sync.
|
||||
--
|
||||
-- The row is the client's `Roles` entry shape:
|
||||
-- { "AccountId": …, "Role": …, "LastChangedByAccountId": …, "InvitedRole": … }
|
||||
-- `role` is the member's CURRENT role tier (10 Host, 20 Moderator, 30 CoOwner,
|
||||
-- 255 Creator); `invited_role` is the tier they have been OFFERED but not yet accepted —
|
||||
-- usually higher than `role`, and 0 when no invitation is pending. `last_changed_by` is
|
||||
-- who last touched the row; NULL for creator entries, matching the blob's
|
||||
-- `LastChangedByAccountId: null`.
|
||||
--
|
||||
-- The table is AUTHORITATIVE and the blob's `Roles` key is removed below, the same
|
||||
-- arrangement 0013 gave tags: `serializeRoom` drops `Roles` on write and the reads
|
||||
-- re-attach it (attachRoles), so the room DTO the client sees is unchanged and the two
|
||||
-- copies can't drift. `getContributedRooms` now matches on this table instead of running
|
||||
-- `json_each` over every blob.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room_role (
|
||||
room_id INTEGER NOT NULL,
|
||||
account_id INTEGER NOT NULL,
|
||||
role INTEGER NOT NULL DEFAULT 0,
|
||||
last_changed_by INTEGER,
|
||||
invited_role INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (room_id, account_id)
|
||||
);
|
||||
|
||||
-- Backfill from the blobs, the way 0013 backfilled `room_tag`. `json_each` walks the
|
||||
-- `Roles` array; a room with no array (or a null one) contributes nothing, which is why
|
||||
-- this is a join rather than a correlated subquery. `INSERT OR IGNORE` collapses a blob
|
||||
-- that somehow carries the same account twice — the primary key is (room, account).
|
||||
INSERT OR IGNORE INTO room_role (room_id, account_id, role, last_changed_by, invited_role)
|
||||
SELECT
|
||||
r.room_id,
|
||||
json_extract(t.value, '$.AccountId'),
|
||||
COALESCE(json_extract(t.value, '$.Role'), 0),
|
||||
json_extract(t.value, '$.LastChangedByAccountId'),
|
||||
COALESCE(json_extract(t.value, '$.InvitedRole'), 0)
|
||||
FROM room r, json_each(r.data, '$.Roles') t
|
||||
WHERE json_extract(t.value, '$.AccountId') IS NOT NULL;
|
||||
|
||||
-- Single source of truth: the roles now live in `room_role`, so the copy in the blob
|
||||
-- goes. Leaving it would be a second answer to "who has a role in this room" that only
|
||||
-- the writes through setRoomRole keep current.
|
||||
UPDATE room SET data = json_remove(data, '$.Roles');
|
||||
@@ -98,6 +98,12 @@ export const playerIdParam = idParam('playerId', 'The account whose list to read
|
||||
/** The `:playerId` path parameter on the unban route. */
|
||||
export const bannedPlayerIdParam = idParam('playerId', 'The banned account to unban')
|
||||
|
||||
/** The `:leaderboardId` path parameter on the room leaderboard routes. */
|
||||
export const leaderboardIdParam = idParam(
|
||||
'leaderboardId',
|
||||
'The leaderboard slot within the room — small ordinals (1, 2, 3…), unique per room only'
|
||||
)
|
||||
|
||||
/** An optional string query parameter. */
|
||||
export function stringQuery(name: string, description: string): OpenAPIV3_1.ParameterObject {
|
||||
return { name, in: 'query', required: false, description, schema: { type: 'string' } }
|
||||
@@ -311,9 +317,6 @@ export const RoomDto = z.object({
|
||||
.int()
|
||||
.nullable()
|
||||
.describe('The room’s published snapshot. Nothing takes snapshots here, so always null'),
|
||||
FriendlyName: z
|
||||
.string()
|
||||
.describe('Display name. Nothing sets one apart from `Name` here, so it mirrors `Name`'),
|
||||
CCU: z
|
||||
.int()
|
||||
.nullable()
|
||||
@@ -609,6 +612,28 @@ export const RoomBanEnvelope = z.object({
|
||||
value: RoomBanDto.nullable().describe('Null on a rejection'),
|
||||
})
|
||||
|
||||
/** `POST /rooms/{roomId}/leaderboards/{leaderboardId}` — configure one leaderboard slot. */
|
||||
export const LeaderboardRequest = z.object({
|
||||
leaderboardTitle: z.string().describe('The title the board displays'),
|
||||
statFormat: z.string().optional().describe('The stat-format int; defaults to 0'),
|
||||
sortAscending: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('`True` / `False` — whether lower scores rank first. Defaults to `False`'),
|
||||
})
|
||||
|
||||
/**
|
||||
* What both leaderboard routes answer: a bare success/failure carrying no entity.
|
||||
* PascalCase `Success`/`Error` with a lowercase `error_id` — the same mixed casing the
|
||||
* unprefixed isBanned envelope has ({@link IsBannedPascalEnvelope}), NOT the room
|
||||
* mutations' lowercase `{ success, error, value }`.
|
||||
*/
|
||||
export const LeaderboardResultEnvelope = z.object({
|
||||
Success: z.boolean(),
|
||||
Error: z.string().nullable().describe('The message shown on a rejection; null on success'),
|
||||
error_id: z.string().nullable().describe('Null. Lowercase, unlike its siblings'),
|
||||
})
|
||||
|
||||
/** `PUT /rooms/{roomId}/warning`. */
|
||||
export const WarningRequest = z.object({
|
||||
warningMask: z.string().describe('Content-warning bit flags, as an integer'),
|
||||
|
||||
+120
-3
@@ -14,6 +14,7 @@ import {
|
||||
countRoomsByCreator,
|
||||
createSubRoom,
|
||||
deleteRoom,
|
||||
deleteRoomLeaderboard,
|
||||
deleteSubRoom,
|
||||
findSubRoom,
|
||||
getBaseRooms,
|
||||
@@ -47,6 +48,7 @@ import {
|
||||
searchRooms,
|
||||
setRoomDescription,
|
||||
setRoomImage,
|
||||
setRoomLeaderboard,
|
||||
setRoomName,
|
||||
setRoomRole,
|
||||
setSubRoomPermissions,
|
||||
@@ -90,6 +92,9 @@ import {
|
||||
IsBannedPascalEnvelope,
|
||||
json,
|
||||
jsonBody,
|
||||
leaderboardIdParam,
|
||||
LeaderboardRequest,
|
||||
LeaderboardResultEnvelope,
|
||||
LoadScreenRequest,
|
||||
MissingLookupParam,
|
||||
ModifySubRoomRequest,
|
||||
@@ -643,6 +648,15 @@ function roomEnvelope(c: Context<App>, value: unknown, error = '') {
|
||||
*/
|
||||
const banEnvelope = roomEnvelope
|
||||
|
||||
/**
|
||||
* The envelope both leaderboard routes answer: `{ Success, Error, error_id }`, carrying
|
||||
* no entity. PascalCase with a lowercase `error_id` — the same mixed casing the
|
||||
* unprefixed isBanned route serves — NOT the room mutations' lowercase envelope.
|
||||
*/
|
||||
function leaderboardEnvelope(c: Context<App>, error: string | null = null) {
|
||||
return c.json({ Success: error === null, Error: error, error_id: null })
|
||||
}
|
||||
|
||||
/** Rooms created/owned by the authed caller (shared by the createdby routes). */
|
||||
async function ownedRooms(c: Context<App>) {
|
||||
const accountId = await authedAccountId(c)
|
||||
@@ -1676,13 +1690,11 @@ const app = new Hono<App>()
|
||||
})
|
||||
}
|
||||
|
||||
// Writes `FriendlyName` too — see `setRoomName`. The two are the same string here,
|
||||
// and the client labels the room from the display one.
|
||||
await setRoomName(c.env.DB, roomId, name)
|
||||
// The rename answers a bare `{ Success }` with no room in it, so the client has
|
||||
// nothing to re-render from and kept showing the old name until the push arrived.
|
||||
// Built from the room already in hand rather than re-read, like the image route's.
|
||||
await pushRoomUpdate(c, accountId, { ...room, Name: name, FriendlyName: name })
|
||||
await pushRoomUpdate(c, accountId, { ...room, Name: name })
|
||||
return roomResult(c, { Success: true })
|
||||
}
|
||||
)
|
||||
@@ -2236,6 +2248,111 @@ const app = new Hono<App>()
|
||||
}
|
||||
)
|
||||
|
||||
// Configure one of a room's leaderboard slots (form body `leaderboardTitle` +
|
||||
// `statFormat` + `sortAscending`). Auth-gated (401) and owner/co-owner-only (403).
|
||||
// One row per (room, slot) — re-posting a slot reconfigures it, so the call is
|
||||
// idempotent.
|
||||
.post(
|
||||
'/rooms/:roomId{[0-9]+}/leaderboards/:leaderboardId{[0-9]+}',
|
||||
describeRoute({
|
||||
tags: ['Room settings'],
|
||||
summary: 'Configure a room leaderboard',
|
||||
description: [
|
||||
'Creates or reconfigures one leaderboard slot in the `room_leaderboard` table — one',
|
||||
'row per (room, slot), so re-posting a slot rewrites its title, format and direction',
|
||||
'rather than adding a second. The slot number in the path is the client’s small',
|
||||
'ordinal (1, 2, 3…), unique only within the room. Owner or co-owner only (403',
|
||||
'otherwise).',
|
||||
'',
|
||||
'`statFormat` is stored verbatim (default 0); `sortAscending` is the client’s',
|
||||
'`True`/`False` string (default `False`).',
|
||||
'',
|
||||
'Answers a bare `{ Success, Error, error_id }` — PascalCase with a lowercase',
|
||||
'`error_id`, like the unprefixed isBanned route, carrying no entity. NOT the room',
|
||||
'mutations’ lowercase `{ success, error, value }`.',
|
||||
].join('\n'),
|
||||
security: AUTHED,
|
||||
parameters: [roomIdParam, leaderboardIdParam],
|
||||
requestBody: form(LeaderboardRequest, 'The leaderboard configuration'),
|
||||
responses: {
|
||||
200: json(LeaderboardResultEnvelope, 'Stored, or a rejection with `Success: false`'),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
403: FORBIDDEN_RESPONSE,
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const accountId = await authedAccountId(c)
|
||||
if (accountId === null) return unauthorized(c)
|
||||
|
||||
const roomId = Number.parseInt(c.req.param('roomId'), 10)
|
||||
const room = await getRoomById(c.env.DB, roomId)
|
||||
if (!room) return leaderboardEnvelope(c, 'This room does not exist!')
|
||||
// A valid token but not the room's owner/co-owner → 403 (the auth gate above
|
||||
// already returned 401 for a missing/invalid token).
|
||||
if (!canManageRoom(room, accountId)) return c.body(null, 403)
|
||||
|
||||
const leaderboardId = Number.parseInt(c.req.param('leaderboardId'), 10)
|
||||
const body = (await c.req.parseBody().catch(() => ({}))) as Record<string, unknown>
|
||||
const title = typeof body.leaderboardTitle === 'string' ? body.leaderboardTitle : ''
|
||||
const statFormat =
|
||||
typeof body.statFormat === 'string' ? Number.parseInt(body.statFormat, 10) : Number.NaN
|
||||
// The client sends .NET-style `True`/`False`; anything but a `true` reads false.
|
||||
const sortAscending =
|
||||
typeof body.sortAscending === 'string' && body.sortAscending.toLowerCase() === 'true'
|
||||
|
||||
await setRoomLeaderboard(
|
||||
c.env.DB,
|
||||
roomId,
|
||||
leaderboardId,
|
||||
title,
|
||||
Number.isNaN(statFormat) ? 0 : statFormat,
|
||||
sortAscending
|
||||
)
|
||||
return leaderboardEnvelope(c)
|
||||
}
|
||||
)
|
||||
|
||||
// Remove one of a room's leaderboard slots. Auth-gated (401) and owner/co-owner-only
|
||||
// (403). The client fires these blindly for every slot when tearing boards down, so a
|
||||
// slot that isn't configured is a rejection envelope, not an HTTP error.
|
||||
.delete(
|
||||
'/rooms/:roomId{[0-9]+}/leaderboards/:leaderboardId{[0-9]+}',
|
||||
describeRoute({
|
||||
tags: ['Room settings'],
|
||||
summary: 'Remove a room leaderboard',
|
||||
description: [
|
||||
'Removes the slot’s `room_leaderboard` row. Owner or co-owner only (403 otherwise).',
|
||||
'',
|
||||
'Removing a slot that isn’t configured is a rejection (`Success: false`), not a',
|
||||
'silent success — the caller asked to undo something that was not there. The client',
|
||||
'deletes slots blindly when tearing boards down and tolerates the refusal.',
|
||||
'',
|
||||
'Answers the same bare `{ Success, Error, error_id }` as the leaderboard write.',
|
||||
].join('\n'),
|
||||
security: AUTHED,
|
||||
parameters: [roomIdParam, leaderboardIdParam],
|
||||
responses: {
|
||||
200: json(LeaderboardResultEnvelope, 'Removed, or a rejection with `Success: false`'),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
403: FORBIDDEN_RESPONSE,
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const accountId = await authedAccountId(c)
|
||||
if (accountId === null) return unauthorized(c)
|
||||
|
||||
const roomId = Number.parseInt(c.req.param('roomId'), 10)
|
||||
const room = await getRoomById(c.env.DB, roomId)
|
||||
if (!room) return leaderboardEnvelope(c, 'This room does not exist!')
|
||||
if (!canManageRoom(room, accountId)) return c.body(null, 403)
|
||||
|
||||
const leaderboardId = Number.parseInt(c.req.param('leaderboardId'), 10)
|
||||
const removed = await deleteRoomLeaderboard(c.env.DB, roomId, leaderboardId)
|
||||
if (!removed) return leaderboardEnvelope(c, 'This room has no such leaderboard!')
|
||||
return leaderboardEnvelope(c)
|
||||
}
|
||||
)
|
||||
|
||||
// Set a room's content warning: the `WarningMask` bit flags plus an optional
|
||||
// free-text `CustomWarning`. Auth-gated (401) and owner/co-owner-only (403). Body is
|
||||
// the `warningMask` form field (an integer) and an optional `customWarning` string
|
||||
|
||||
@@ -156,16 +156,16 @@ describe('rooms endpoints', () => {
|
||||
|
||||
// None of these are stored — the seed blobs predate the keys — so they are defaulted on
|
||||
// read. The client's room DTO always carries them, and an ABSENT key is not the same as a
|
||||
// zero/null one to its parser. `FriendlyName` is the one that can't be null: the client
|
||||
// labels the room from it.
|
||||
it('GET /rooms/:id carries BoostCount, CurrentSnapshotId, FriendlyName and CCU', async () => {
|
||||
// zero/null one to its parser. `FriendlyName` is deliberately NOT among them: this server
|
||||
// does not serve a display name apart from `Name`, and migration 0017 strips any stored one.
|
||||
it('GET /rooms/:id carries BoostCount, CurrentSnapshotId and CCU, and no FriendlyName', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/1`)
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as Record<string, unknown>
|
||||
expect(body).toHaveProperty('BoostCount', 0)
|
||||
expect(body).toHaveProperty('CurrentSnapshotId', null)
|
||||
expect(body).toHaveProperty('FriendlyName', body.Name)
|
||||
expect(body).toHaveProperty('CCU', null)
|
||||
expect(body).not.toHaveProperty('FriendlyName')
|
||||
})
|
||||
|
||||
// Pinned whole: these are the numbers the client's publish UI counts against, and
|
||||
@@ -425,6 +425,11 @@ describe('rooms endpoints', () => {
|
||||
it('GET /rooms/contributedby/me lists rooms the caller owns or has a role in', async () => {
|
||||
const seed = (data: Record<string, unknown>) =>
|
||||
env.DB.prepare('INSERT INTO room (data) VALUES (?1)').bind(JSON.stringify(data)).run()
|
||||
// Roles live in `room_role`, not the blob — the table the query's EXISTS matches on.
|
||||
const grant = (roomId: number, accountId: number, role: number) =>
|
||||
env.DB.prepare('INSERT INTO room_role (room_id, account_id, role) VALUES (?1, ?2, ?3)')
|
||||
.bind(roomId, accountId, role)
|
||||
.run()
|
||||
|
||||
// A room somebody else made, where 820 is a co-owner...
|
||||
await seed({
|
||||
@@ -433,11 +438,9 @@ describe('rooms endpoints', () => {
|
||||
CreatorAccountId: 821,
|
||||
Accessibility: 1,
|
||||
SubRooms: [],
|
||||
Roles: [
|
||||
{ AccountId: 821, Role: 255 },
|
||||
{ AccountId: 820, Role: 30 },
|
||||
],
|
||||
})
|
||||
await grant(30401, 821, 255)
|
||||
await grant(30401, 820, 30)
|
||||
// ...one where they're only a host (every tier counts, not just owner-level)...
|
||||
await seed({
|
||||
RoomId: 30402,
|
||||
@@ -447,20 +450,20 @@ describe('rooms endpoints', () => {
|
||||
// accessibility is not filtered here.
|
||||
Accessibility: 0,
|
||||
SubRooms: [],
|
||||
Roles: [{ AccountId: 820, Role: 10 }],
|
||||
})
|
||||
// ...one they created themselves, whose Roles name them as Creator (matched by BOTH
|
||||
// halves of the query, so it must still appear exactly once)...
|
||||
await grant(30402, 820, 10)
|
||||
// ...one they created themselves, whose role rows name them as Creator (matched by
|
||||
// BOTH halves of the query, so it must still appear exactly once)...
|
||||
await seed({
|
||||
RoomId: 30403,
|
||||
Name: 'ContribOwn',
|
||||
CreatorAccountId: 820,
|
||||
Accessibility: 1,
|
||||
SubRooms: [],
|
||||
Roles: [{ AccountId: 820, Role: 255 }],
|
||||
})
|
||||
// ...one they created that names nobody in Roles at all — the older rooms have no
|
||||
// Roles key, and those reach the list on the creator half alone...
|
||||
await grant(30403, 820, 255)
|
||||
// ...one they created that has no role rows at all — the older rooms never got
|
||||
// any, and those reach the list on the creator half alone...
|
||||
await seed({
|
||||
RoomId: 30406,
|
||||
Name: 'ContribOwnNoRoles',
|
||||
@@ -476,18 +479,17 @@ describe('rooms endpoints', () => {
|
||||
IsDorm: true,
|
||||
Accessibility: 2,
|
||||
SubRooms: [],
|
||||
Roles: [{ AccountId: 820, Role: 255 }],
|
||||
})
|
||||
// ...one they have nothing to do with, and one with no Roles key at all (the older
|
||||
// seeded rooms have none — json_each must drop them, not error).
|
||||
await grant(30407, 820, 255)
|
||||
// ...one they have nothing to do with, and one with no role rows at all.
|
||||
await seed({
|
||||
RoomId: 30404,
|
||||
Name: 'ContribOther',
|
||||
CreatorAccountId: 821,
|
||||
Accessibility: 1,
|
||||
SubRooms: [],
|
||||
Roles: [{ AccountId: 822, Role: 30 }],
|
||||
})
|
||||
await grant(30404, 822, 30)
|
||||
await seed({ RoomId: 30405, Name: 'ContribNoRoles', CreatorAccountId: 821, SubRooms: [] })
|
||||
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/contributedby/me`, {
|
||||
@@ -515,6 +517,7 @@ describe('rooms endpoints', () => {
|
||||
// The DB is shared across this file, and these are the only player-made public rooms
|
||||
// in it — leaving them behind changes what the `new`/`community` room feeds serve.
|
||||
await env.DB.prepare('DELETE FROM room WHERE room_id BETWEEN 30401 AND 30407').run()
|
||||
await env.DB.prepare('DELETE FROM room_role WHERE room_id BETWEEN 30401 AND 30407').run()
|
||||
})
|
||||
|
||||
it('GET /rooms/:roomId/experience serves the fixed XP settings, no auth', async () => {
|
||||
@@ -1818,6 +1821,90 @@ describe('rooms endpoints', () => {
|
||||
.run()
|
||||
})
|
||||
|
||||
it('POST/DELETE /rooms/:id/leaderboards/:lid configures and removes a room’s leaderboard slots', async () => {
|
||||
// RecCenter (room 2) is owned by account 1, with account 2 as co-owner.
|
||||
const del = async (path: string, sub?: string) =>
|
||||
SELF.fetch(`${ORIGIN}${path}`, {
|
||||
method: 'DELETE',
|
||||
headers: sub ? await bearer(sub) : {},
|
||||
})
|
||||
const slotsOf = async (roomId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT leaderboard_id, leaderboard_title, stat_format, sort_ascending FROM room_leaderboard WHERE room_id = ?1 ORDER BY leaderboard_id'
|
||||
)
|
||||
.bind(roomId)
|
||||
.all()
|
||||
).results
|
||||
|
||||
// The real client body, verbatim.
|
||||
const body = { leaderboardTitle: 'full name', statFormat: '1', sortAscending: 'False' }
|
||||
|
||||
// No token → 401 (auth gate).
|
||||
expect((await postForm('/rooms/2/leaderboards/1', body)).status).toBe(401)
|
||||
expect((await del('/rooms/2/leaderboards/1')).status).toBe(401)
|
||||
// A valid token but no role on the room → 403.
|
||||
expect((await postForm('/rooms/2/leaderboards/1', body, '999')).status).toBe(403)
|
||||
expect((await del('/rooms/2/leaderboards/1', '999')).status).toBe(403)
|
||||
// The envelope both routes answer — PascalCase `Success`/`Error`, lowercase
|
||||
// `error_id`, no entity. NOT the room mutations' lowercase `{ success, error, value }`.
|
||||
const OK = { Success: true, Error: null, error_id: null }
|
||||
|
||||
// Unknown room → failure envelope.
|
||||
expect(await (await postForm('/rooms/99999/leaderboards/1', body, '1')).json()).toEqual({
|
||||
Success: false,
|
||||
Error: 'This room does not exist!',
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
// The owner configures slot 1 — a bare success, with the row persisted.
|
||||
const ok = await postForm('/rooms/2/leaderboards/1', body, '1')
|
||||
expect(ok.status).toBe(200)
|
||||
expect(await ok.json()).toEqual(OK)
|
||||
expect(await slotsOf(2)).toEqual([
|
||||
{ leaderboard_id: 1, leaderboard_title: 'full name', stat_format: 1, sort_ascending: 0 },
|
||||
])
|
||||
|
||||
// Re-posting the slot reconfigures the one row rather than appending — and the
|
||||
// co-owner may do it. `sortAscending=True` parses case-insensitively.
|
||||
const rewrite = await postForm(
|
||||
'/rooms/2/leaderboards/1',
|
||||
{ leaderboardTitle: 'lap time', statFormat: '2', sortAscending: 'True' },
|
||||
'2'
|
||||
)
|
||||
expect(rewrite.status).toBe(200)
|
||||
expect(await rewrite.json()).toEqual(OK)
|
||||
expect(await slotsOf(2)).toEqual([
|
||||
{ leaderboard_id: 1, leaderboard_title: 'lap time', stat_format: 2, sort_ascending: 1 },
|
||||
])
|
||||
|
||||
// Slots are per room: slot 2 here and slot 1 of another room are their own rows.
|
||||
expect((await postForm('/rooms/2/leaderboards/2', body, '1')).status).toBe(200)
|
||||
expect((await postForm('/rooms/3/leaderboards/1', body, '1')).status).toBe(200)
|
||||
expect(await slotsOf(2)).toHaveLength(2)
|
||||
expect(await slotsOf(3)).toHaveLength(1)
|
||||
|
||||
// DELETE removes exactly the named slot.
|
||||
const removed = await del('/rooms/2/leaderboards/1', '1')
|
||||
expect(removed.status).toBe(200)
|
||||
expect(await removed.json()).toEqual(OK)
|
||||
expect(await slotsOf(2)).toEqual([
|
||||
{ leaderboard_id: 2, leaderboard_title: 'full name', stat_format: 1, sort_ascending: 0 },
|
||||
])
|
||||
expect(await slotsOf(3)).toHaveLength(1)
|
||||
|
||||
// Deleting a slot that isn't configured is a rejection, not an HTTP error — the
|
||||
// client tears boards down by deleting every slot blindly.
|
||||
expect(await (await del('/rooms/2/leaderboards/1', '1')).json()).toEqual({
|
||||
Success: false,
|
||||
Error: 'This room has no such leaderboard!',
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
// Clean up the surviving rows so this test leaves no trace.
|
||||
await env.DB.prepare('DELETE FROM room_leaderboard WHERE room_id IN (2, 3)').run()
|
||||
})
|
||||
|
||||
it('POST /rooms/:id/bans kicks the banned player', async () => {
|
||||
type Sent = { playerId: number; notificationType: string | number; data: unknown }
|
||||
const hub = () => env.RECFLARE_NOTIFICATIONS_HUB.getByName('global')
|
||||
@@ -2915,13 +3002,12 @@ describe('rooms endpoints', () => {
|
||||
expect(await bodyOf(ok)).toMatchObject({ Success: true })
|
||||
const room = (await (await SELF.fetch(`${ORIGIN}/rooms?name=RenamedCenter`)).json()) as {
|
||||
RoomId: number
|
||||
FriendlyName: string
|
||||
Name: string
|
||||
}
|
||||
expect(room.RoomId).toBe(2)
|
||||
// The DISPLAY name follows the rename. It is otherwise only defaulted to `Name` on
|
||||
// read, so a room that had ever stored one would keep labelling itself with the old
|
||||
// name while every name-keyed lookup used the new one.
|
||||
expect(room.FriendlyName).toBe('RenamedCenter')
|
||||
expect(room.Name).toBe('RenamedCenter')
|
||||
// A rename must not resurrect the retired display name.
|
||||
expect(room).not.toHaveProperty('FriendlyName')
|
||||
})
|
||||
|
||||
/** The hub stub records every notifyPlayer call — see vitest.config.ts. */
|
||||
@@ -2941,14 +3027,11 @@ describe('rooms endpoints', () => {
|
||||
|
||||
const sent = await sentNotifications()
|
||||
expect(sent).toHaveLength(1)
|
||||
// RoomUpdate, to the OWNER, carrying the room as it now stands — both names.
|
||||
// RoomUpdate, to the OWNER, carrying the room as it now stands.
|
||||
expect(sent[0].playerId).toBe(1)
|
||||
expect(sent[0].notificationType).toBe(NotificationType.SubscriptionUpdateRoom)
|
||||
expect(sent[0].data).toMatchObject({
|
||||
RoomId: 2,
|
||||
Name: 'PushedRename',
|
||||
FriendlyName: 'PushedRename',
|
||||
})
|
||||
expect(sent[0].data).toMatchObject({ RoomId: 2, Name: 'PushedRename' })
|
||||
expect(sent[0].data).not.toHaveProperty('FriendlyName')
|
||||
|
||||
// Put it back for the tests that read room 2 by name.
|
||||
await putForm('/rooms/2/name', { name: 'RenamedCenter' }, '1')
|
||||
@@ -3972,6 +4055,7 @@ describe('rooms endpoints', () => {
|
||||
'DELETE /rooms/{roomId}/bans/{playerId}',
|
||||
'DELETE /rooms/{roomId}/interactionby/me/cheer',
|
||||
'DELETE /rooms/{roomId}/interactionby/me/favorite',
|
||||
'DELETE /rooms/{roomId}/leaderboards/{leaderboardId}',
|
||||
'DELETE /rooms/{roomId}/subrooms/{subRoomId}',
|
||||
'GET /',
|
||||
'GET /Room_server/rooms/{roomId}/bans/{playerId}/isBanned',
|
||||
@@ -4010,6 +4094,7 @@ describe('rooms endpoints', () => {
|
||||
'POST /rooms/bulk',
|
||||
'POST /rooms/{roomId}/bans',
|
||||
'POST /rooms/{roomId}/clone',
|
||||
'POST /rooms/{roomId}/leaderboards/{leaderboardId}',
|
||||
'POST /rooms/{roomId}/subrooms',
|
||||
'POST /rooms/{roomId}/subrooms/{subRoomId}/clone',
|
||||
'POST /rooms/{roomId}/subrooms/{subRoomId}/data',
|
||||
|
||||
@@ -4563,6 +4563,110 @@
|
||||
"LoadScreens": [],
|
||||
"RestrictedCircuitsAllowListNames": []
|
||||
},
|
||||
{
|
||||
"RoomId": 45,
|
||||
"Name": "RecRally",
|
||||
"Description": "A six-player off-road rally race where you boost, bump, and powerslide your way to the finish line!",
|
||||
"ImageName": "RecRally.jpg",
|
||||
"WarningMask": 8,
|
||||
"CustomWarning": null,
|
||||
"CreatorAccountId": 1,
|
||||
"State": 0,
|
||||
"Accessibility": 1,
|
||||
"PublishState": 0,
|
||||
"SupportsLevelVoting": false,
|
||||
"IsRRO": true,
|
||||
"IsRecRoomApproved": false,
|
||||
"ExcludeFromLists": false,
|
||||
"ExcludeFromSearch": false,
|
||||
"SupportsScreens": true,
|
||||
"SupportsWalkVR": true,
|
||||
"SupportsTeleportVR": true,
|
||||
"SupportsVRLow": true,
|
||||
"SupportsQuest2": true,
|
||||
"SupportsMobile": true,
|
||||
"SupportsJuniors": true,
|
||||
"MinLevel": 0,
|
||||
"AgeRating": 2,
|
||||
"CreatedAt": "2026-02-25T20:10:07.201204Z",
|
||||
"PublishedAt": "2026-02-25T20:10:07.201204Z",
|
||||
"BecameRRStudioRoomAt": null,
|
||||
"Stats": {
|
||||
"CheerCount": 0,
|
||||
"FavoriteCount": 0,
|
||||
"VisitorCount": 0,
|
||||
"VisitCount": 0
|
||||
},
|
||||
"RankingContext": null,
|
||||
"IsDorm": false,
|
||||
"IsPlacePlay": false,
|
||||
"MaxPlayerCalculationMode": 0,
|
||||
"MaxPlayers": 6,
|
||||
"CloningAllowed": true,
|
||||
"DisableMicAutoMute": false,
|
||||
"DisableRoomComments": false,
|
||||
"EncryptVoiceChat": false,
|
||||
"ToxmodEnabled": true,
|
||||
"LoadScreenLocked": false,
|
||||
"UgcVersion": 1,
|
||||
"PersistenceVersion": 0,
|
||||
"UgcSubVersion": null,
|
||||
"MinUgcSubVersion": null,
|
||||
"AutoLocalizeRoom": false,
|
||||
"LocalizationContext": {
|
||||
"TargetLocale": null,
|
||||
"Scope": null,
|
||||
"LocalizedFields": [
|
||||
"Description",
|
||||
"CustomWarning"
|
||||
]
|
||||
},
|
||||
"IsDeveloperOwned": true,
|
||||
"RankedEntityId": "45",
|
||||
"BoostCount": 0,
|
||||
"CurrentSnapshotId": null,
|
||||
"SubRooms": [
|
||||
{
|
||||
"SubRoomId": 57,
|
||||
"RoomId": 45,
|
||||
"CreatorAccountId": null,
|
||||
"UnitySceneId": "56193568-9ae0-498c-8a77-4df79dec91f5",
|
||||
"Name": "Home",
|
||||
"CurrentSave": null,
|
||||
"LastModeratedSaveModerationState": 0,
|
||||
"IsSandbox": false,
|
||||
"MaxPlayers": 6,
|
||||
"Accessibility": 1,
|
||||
"ShouldAutoStageSaves": true,
|
||||
"StagedSubRoomDataSaveId": null
|
||||
}
|
||||
],
|
||||
"Roles": [
|
||||
{
|
||||
"AccountId": 1,
|
||||
"Role": 255,
|
||||
"LastChangedByAccountId": null,
|
||||
"InvitedRole": 0
|
||||
},
|
||||
{
|
||||
"AccountId": 2,
|
||||
"Role": 30,
|
||||
"LastChangedByAccountId": null,
|
||||
"InvitedRole": 0
|
||||
}
|
||||
],
|
||||
"IsJuniorCreated": false,
|
||||
"Tags": [
|
||||
{
|
||||
"Tag": "rro",
|
||||
"Type": 0
|
||||
}
|
||||
],
|
||||
"PromoImages": [],
|
||||
"PromoExternalContent": [],
|
||||
"LoadScreens": [],
|
||||
"RestrictedCircuitsAllowListNames": []
|
||||
},
|
||||
{
|
||||
"RoomId": 51,
|
||||
"Name": "DriveIn",
|
||||
|
||||
@@ -81,6 +81,125 @@ printf '1x0000000000000000000000000000000AA' |
|
||||
|
||||
The tests seed the same pair into their own local store in `beforeAll`.
|
||||
|
||||
### Benefits claim and Discord
|
||||
|
||||
The **Claim benefits** tab on the account page lets a player prove they hold one of
|
||||
the qualifying roles in the community Discord and, if they do, gives their account Rec
|
||||
Room Plus (`account.hasPlus`). The same panel also renders at `/claim`, which is the
|
||||
app's registered `redirect_uri` — Discord sends the browser back there mid-flow, so
|
||||
that route has to keep working on a cold load even though nothing links to it. It runs a standard
|
||||
OAuth2 **authorization code** flow:
|
||||
|
||||
1. The tab sends the browser to Discord's consent screen, using the URL `www`
|
||||
assembles in `/api/config` plus a `state` nonce the page mints and stashes in
|
||||
`sessionStorage`.
|
||||
2. Discord redirects back to `/claim?code=…&state=…`. The page checks the nonce is
|
||||
the one it minted, strips the query, and posts only the `code` to
|
||||
`POST /api/benefits/claim` with the player's bearer token.
|
||||
3. The worker swaps the code for an access token with the client secret, reads
|
||||
`GET /users/@me/guilds/{guild}/member` to get the player's roles, revokes the
|
||||
token, and — if any one of the configured roles is there — writes `hasPlus` onto
|
||||
the account and links the Discord id.
|
||||
|
||||
**A claim takes effect on the player's next sign-in, not immediately.** `auth` stamps
|
||||
`hasPlus` into every token it mints as the `rn.plus` claim, and `econ` decides the
|
||||
CampusCard and the subscriber discount from that claim alone — no database read on
|
||||
either path. The token the player's game is holding was minted before they claimed, it
|
||||
lasts a day, and the client never refreshes it, so they have to restart Rec Room and
|
||||
sign in again. The claim page says so.
|
||||
|
||||
The browser never holds a Discord access token: the client secret can't ship to a
|
||||
page, which is why this is the second feature (after signup) with a server side.
|
||||
The scopes are `identify` and `guilds.members.read`, which let the token's owner
|
||||
read **their own** membership in one guild — so no bot is needed and this worker
|
||||
holds no credential that could read anyone else's roles.
|
||||
|
||||
The verified Discord id is stored as a link in `platform_account` (the `auth`
|
||||
worker's table of account ↔ external identities, migration 0007) under
|
||||
`PlatformType.Discord` (101) — the same place a Steam or Meta identity lives,
|
||||
because that is what it is. Only `hasPlus` goes on the account itself.
|
||||
|
||||
Nobody logs in with it. `auth`'s `verifyPlatformProof` can prove exactly two
|
||||
platforms (Steam and Meta), so a `cached_login` naming 101 is refused outright, and
|
||||
the login picker filters to those same platforms (`CACHED_LOGIN_PLATFORMS`). That
|
||||
filter matters for privacy as well as correctness: the picker is public and
|
||||
unauthenticated, so without it `GET /cachedlogin/forplatformid/101/<snowflake>`
|
||||
would tell anyone which RecFlare account a given Discord user owns.
|
||||
|
||||
Storing the link there is what makes the claim once-only **per Discord user**, not
|
||||
per account: a second claim from the same Discord member on a different account is
|
||||
refused (409), answered from the table's index rather than a scan of every account
|
||||
blob. Re-claiming on the same account is idempotent — the link is `INSERT OR
|
||||
IGNORE`, so `linkedAt` keeps the first claim's time — so the page is safe to
|
||||
reload. Nothing revokes Plus: losing the role later leaves the flag set, so it
|
||||
records "held the role once", not "holds it today".
|
||||
|
||||
Four settings configure it, and **all four** are required or the claim stays
|
||||
closed (`/api/config` reports `benefitsEnabled: false`, so the SPA hides the tab,
|
||||
and `/api/benefits/claim` returns 403). A half-configured app is
|
||||
treated as unconfigured on purpose: a client id and secret with no guild/roles
|
||||
would authenticate a player and have no question left to ask about them.
|
||||
|
||||
- `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` — Secrets Store, same account-level
|
||||
store as `JWT_SECRET` and the Turnstile pair. The id is public (it ships to the
|
||||
browser inside the authorize URL) but lives beside its secret so one place
|
||||
configures the feature.
|
||||
- `DISCORD_GUILD_ID` / `DISCORD_BENEFITS_ROLE_IDS` — plain vars in `wrangler.jsonc`,
|
||||
not credentials. Both hold Discord **snowflakes: all digits, no letters**. Turn on
|
||||
Developer Mode in Discord (Settings → Advanced), then right-click the server or the
|
||||
role and Copy ID. These are ids, not names — `Supporter` is what the role is
|
||||
_called_, `1077000000000000002` is what goes in the var — and they're quoted as
|
||||
strings because a snowflake is too large to survive as a JSON number.
|
||||
|
||||
`DISCORD_BENEFITS_ROLE_IDS` is a **list**, separated by commas and/or whitespace, so
|
||||
several tiers can qualify for the same benefit. **Any one** of them is enough — they
|
||||
are alternatives, not requirements:
|
||||
|
||||
```jsonc
|
||||
"DISCORD_BENEFITS_ROLE_IDS": "1077000000000000001,1077000000000000002"
|
||||
```
|
||||
|
||||
Blank entries are dropped, so a trailing comma is harmless. A value that parses to no
|
||||
ids at all counts as unset and closes the claim, rather than opening it with nothing
|
||||
to check against.
|
||||
|
||||
```sh
|
||||
printf '<client id>' |
|
||||
wrangler secrets-store secret create <store-id> --name DISCORD_CLIENT_ID --scopes workers --remote
|
||||
printf '<client secret>' |
|
||||
wrangler secrets-store secret create <store-id> --name DISCORD_CLIENT_SECRET --scopes workers --remote
|
||||
```
|
||||
|
||||
The two ids are **not secrets**, and setting only the secrets is the usual reason the
|
||||
page never appears. Put them in the root `.env` as operator knobs, where they ride
|
||||
along as `--var` on deploy (see `recflare_vars`), rather than editing `wrangler.jsonc`
|
||||
— that keeps your server's ids out of the repo:
|
||||
|
||||
```sh
|
||||
RECFLARE_DISCORD_GUILD_ID=1077000000000000000
|
||||
RECFLARE_DISCORD_BENEFITS_ROLE_IDS=1077000000000000001,1077000000000000002
|
||||
```
|
||||
|
||||
Use **commas with no spaces** there. Those knobs become `--var` flags that the deploy
|
||||
script word-splits, so a value containing a space breaks it. (`parseRoleIds` also
|
||||
accepts whitespace, which is fine in `wrangler.jsonc` but not via `.env`.)
|
||||
|
||||
Then redeploy `www` — the Secrets Store `.get()` caches per isolate, so a warm worker
|
||||
won't pick up newly created secrets until it restarts.
|
||||
|
||||
**Diagnosing a claim that won't appear:** fetch `/api/config`. If `benefitsEnabled` is
|
||||
`false`, the gate is closed and it isn't a UI problem — `www` logs
|
||||
`discord is half-configured, so benefit claims are closed` with a flag per input
|
||||
(`hasClientId`, `hasClientSecret`, `hasGuildId`, `roleIdCount`), which names exactly
|
||||
which one is missing. `wrangler tail www` shows it.
|
||||
|
||||
In the [Discord developer portal](https://discord.com/developers/applications),
|
||||
add `https://<your domain>/claim` to the app's **Redirects**. It has to match byte
|
||||
for byte: `www` derives the redirect URI from the incoming request's own origin
|
||||
(never from the request body, which would turn the client secret into a redemption
|
||||
oracle for someone else's app), so add `http://localhost:5173/claim` too if you
|
||||
want the flow to work under `pnpm turbo dev`.
|
||||
|
||||
## Development
|
||||
|
||||
### Run in dev mode
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"dependencies": {
|
||||
"@repo/domain": "workspace:*",
|
||||
"@repo/hono-helpers": "workspace:*",
|
||||
"@repo/jwt": "workspace:*",
|
||||
"@scalar/api-reference": "1.63.0",
|
||||
"hono": "4.12.27",
|
||||
"react": "19.2.7",
|
||||
|
||||
@@ -32,13 +32,11 @@ const AUTH_MESSAGES: Record<string, string> = {
|
||||
'no linked account for this platform identity':
|
||||
'No account is linked to this platform sign-in yet. Sign in with your password once to link it.',
|
||||
'refresh_token is invalid or expired': 'Your session has expired. Please sign in again.',
|
||||
// Deliberately says nothing about when it lifts: auth sends one fixed description for
|
||||
// every ban (see its BANNED_DESCRIPTION), permanent or timed, so there is no expiry
|
||||
// here to quote.
|
||||
'this account is banned': 'This account is banned and cannot be signed in to.',
|
||||
// Not this account, but one it shares a device or network with. Phrased for BOTH the
|
||||
// An account that shares a device or network with a BANNED one. Phrased for BOTH the
|
||||
// person evading a ban and the housemate of one — the IP arm cannot tell them apart —
|
||||
// and for both forms, since signup and sign-in send the same description.
|
||||
// and for both forms, since signup and sign-in send the same description. A directly
|
||||
// banned account is not refused a sign-in at all (auth issues it a token so the game
|
||||
// client can show the block screen), which is why there is no "banned" entry here.
|
||||
'this device or network is blocked':
|
||||
'This device or network is blocked. If you think that is a mistake, contact the server operator.',
|
||||
}
|
||||
|
||||
+375
-17
@@ -46,6 +46,18 @@ interface Hosts {
|
||||
interface SiteConfig {
|
||||
signupEnabled: boolean
|
||||
turnstileSiteKey: string | null
|
||||
/**
|
||||
* Whether the Discord-verified benefits claim is configured. False when the operator
|
||||
* has no Discord app/guild/role set, in which case the claim page and its links stay
|
||||
* hidden — the endpoint would refuse anyway.
|
||||
*/
|
||||
benefitsEnabled: boolean
|
||||
/**
|
||||
* The Discord consent URL to send the player to, assembled by `www` (scopes and the
|
||||
* redirect URI are its business, and must match what the claim will accept). Null when
|
||||
* benefits are off. The `state` nonce is appended here — see `startDiscordAuth`.
|
||||
*/
|
||||
discordAuthorizeUrl: string | null
|
||||
}
|
||||
|
||||
/** The private self DTO from `accounts` (`GET /account/me`). */
|
||||
@@ -495,6 +507,50 @@ const changePassword = (oldPassword: string, newPassword: string): Promise<unkno
|
||||
authed: true,
|
||||
})
|
||||
|
||||
/** Where this account's benefits stand: `www` reads them off the account row. */
|
||||
interface BenefitsStatus {
|
||||
/** Whether the account already has Rec Room Plus. */
|
||||
hasPlus: boolean
|
||||
/** Whether a Discord identity is already tied to it. Which one is deliberately not served. */
|
||||
linked: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The two ends of the benefits claim. Both live on `www` rather than on one of the game
|
||||
* workers, because the claim needs the Discord client secret — see www.app.ts.
|
||||
*/
|
||||
const fetchBenefitsStatus = (): Promise<BenefitsStatus> =>
|
||||
call<BenefitsStatus>('/api/benefits/status', { authed: true })
|
||||
|
||||
/** Redeem the code Discord sent us back with. The access token never reaches this page. */
|
||||
const claimBenefits = (code: string): Promise<{ discordUsername?: string }> =>
|
||||
call<{ discordUsername?: string }>('/api/benefits/claim', { json: { code }, authed: true })
|
||||
|
||||
/**
|
||||
* The per-attempt CSRF nonce for the Discord round-trip, in sessionStorage.
|
||||
*
|
||||
* OAuth's `state` only means anything if the same page that minted it is the one that
|
||||
* checks it, so it can't come from the server. sessionStorage rather than localStorage:
|
||||
* it belongs to this tab and this attempt, and it should not outlive the tab that started
|
||||
* the flow.
|
||||
*/
|
||||
const OAUTH_STATE_KEY = 'rf_discord_state'
|
||||
|
||||
/**
|
||||
* Send the browser to Discord's consent screen.
|
||||
*
|
||||
* A real navigation, not a client-side route — Discord is another origin. The `state` is
|
||||
* minted here and stashed for the return leg; `www` built everything else about the URL
|
||||
* (see `/api/config`), so this only ever appends the one parameter it owns.
|
||||
*/
|
||||
function startDiscordAuth(authorizeUrl: string) {
|
||||
const state = crypto.randomUUID()
|
||||
sessionStorage.setItem(OAUTH_STATE_KEY, state)
|
||||
const url = new URL(authorizeUrl)
|
||||
url.searchParams.set('state', state)
|
||||
window.location.assign(url.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin-only broadcasts. The token goes to `notify`, which enforces the admin-role gate
|
||||
* — so a session without the role is rejected there (403) even though the UI shows no
|
||||
@@ -516,6 +572,42 @@ const coachMessageAll = (messageContent: string): Promise<{ sent?: number }> =>
|
||||
authed: true,
|
||||
})
|
||||
|
||||
/**
|
||||
* The same coach message to ONE player. `notify` queues it when they're offline, so
|
||||
* `queued` (rather than a 0 delivery) is what "they weren't online" looks like here —
|
||||
* it still arrives on their next connect, unlike the broadcast.
|
||||
*/
|
||||
const coachMessage = (
|
||||
playerId: number,
|
||||
messageContent: string
|
||||
): Promise<{ delivered?: number; queued?: boolean }> =>
|
||||
call<{ delivered?: number; queued?: boolean }>(`${where().notify}/internal/coach-message`, {
|
||||
json: { playerId, messageContent },
|
||||
authed: true,
|
||||
})
|
||||
|
||||
/**
|
||||
* Resolve an `@username` to the account id the workers address a player by.
|
||||
*
|
||||
* `accounts` serves no exact-name lookup, so this goes through the PREFIX search and
|
||||
* keeps only an exact (case-insensitive) hit: a prefix match is a different player, and
|
||||
* sending a message to whoever happened to sort first would be worse than refusing. The
|
||||
* exact name always sorts first among its own prefixes, so it's inside the search limit
|
||||
* whenever it exists.
|
||||
*/
|
||||
async function accountIdForUsername(input: string): Promise<number> {
|
||||
const name = input.trim().replace(/^@/, '')
|
||||
if (name === '') throw new Error('Enter a username to send to.')
|
||||
const matches = await call<Array<{ accountId?: number; username?: string }>>(
|
||||
`${where().accounts}/account/search?name=${encodeURIComponent(name)}`
|
||||
)
|
||||
const found = Array.isArray(matches)
|
||||
? matches.find((m) => m.username?.toLowerCase() === name.toLowerCase())
|
||||
: undefined
|
||||
if (typeof found?.accountId !== 'number') throw new Error(`There's no player called @${name}.`)
|
||||
return found.accountId
|
||||
}
|
||||
|
||||
/** Minimal history-based router: current pathname + a navigate() that pushes state. */
|
||||
function useRouter() {
|
||||
const [path, setPath] = useState(() => window.location.pathname)
|
||||
@@ -562,6 +654,214 @@ function Link({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The benefits claim itself: where the player stands, and the button that starts (or
|
||||
* re-runs) the Discord round-trip.
|
||||
*
|
||||
* This is BOTH ends of the OAuth round-trip: it sends the player to Discord, and it is
|
||||
* what renders when Discord sends them back. Which half is running is decided by whether
|
||||
* the URL carries a `code`.
|
||||
*
|
||||
* What it never holds is a Discord access token. It forwards the one-time `code` to
|
||||
* `www`, which does the exchange with the client secret and answers with a verdict; that
|
||||
* is the whole reason this one feature has a server side at all.
|
||||
*
|
||||
* Rendered in TWO places, which is why it is a component rather than a page. Its home is
|
||||
* the "Claim benefits" tab in the account dashboard, where someone would go looking for
|
||||
* it. But it also has to render on `/claim`, because that path is Discord's registered
|
||||
* redirect URI — the browser comes back to it with a `?code=`, and it is the only URL a
|
||||
* cold load can land on mid-flow. One component means the two can't drift.
|
||||
*
|
||||
* The effect keys off whether the URL carries a code, so the same code covers both: on
|
||||
* the dashboard there is none, and it just reports status.
|
||||
*/
|
||||
function BenefitsPanel({ account, config }: { account: SelfAccount; config: SiteConfig }) {
|
||||
const [status, setStatus] = useState<BenefitsStatus | undefined>(undefined)
|
||||
const [error, setError] = useState('')
|
||||
const [done, setDone] = useState('')
|
||||
const [pending, setPending] = useState(false)
|
||||
// Shown after a successful claim only. Plus rides on the game's token as `rn.plus`,
|
||||
// stamped at login, so the copy of it the player is holding still says they have none —
|
||||
// and tokens last a day and are never refreshed. Without this line the claim looks like
|
||||
// it silently did nothing, which is the single most likely support question here.
|
||||
const [relogin, setRelogin] = useState(false)
|
||||
// StrictMode runs effects twice in dev, and a Discord code is single-use: the second
|
||||
// run would redeem a spent code and report a failure over a claim that just worked.
|
||||
const redeemed = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const code = params.get('code')
|
||||
const state = params.get('state')
|
||||
const expected = sessionStorage.getItem(OAUTH_STATE_KEY)
|
||||
|
||||
if (code === null) {
|
||||
// Nothing came back from Discord — either the dashboard tab, or `/claim` opened
|
||||
// directly. Just show where they stand. Discord also returns with
|
||||
// `?error=access_denied` when someone cancels: no code, nothing to say, and the
|
||||
// button is right there to try again.
|
||||
void fetchBenefitsStatus()
|
||||
.then(setStatus)
|
||||
.catch(() => setStatus(undefined))
|
||||
return
|
||||
}
|
||||
|
||||
// The return leg. Strip the query first, whatever happens next: the code is spent by
|
||||
// the request below, so a reload must not carry it (and a code has no business
|
||||
// sitting in the address bar, or in whatever the player pastes it into). replaceState
|
||||
// rather than a route change, so Back doesn't walk into a used code either.
|
||||
window.history.replaceState(null, '', '/claim')
|
||||
if (redeemed.current) return
|
||||
redeemed.current = true
|
||||
sessionStorage.removeItem(OAUTH_STATE_KEY)
|
||||
|
||||
// The nonce this tab minted must be the one that came back. A mismatch means the
|
||||
// round-trip wasn't started here, which is exactly what `state` exists to catch.
|
||||
if (state === null || expected === null || state !== expected) {
|
||||
setError('That Discord sign-in did not match this browser. Please start again.')
|
||||
return
|
||||
}
|
||||
|
||||
setPending(true)
|
||||
claimBenefits(code)
|
||||
.then((result) => {
|
||||
setStatus({ hasPlus: true, linked: true })
|
||||
setDone(
|
||||
result.discordUsername
|
||||
? `Verified as ${result.discordUsername} — Rec Room Plus is now on your account.`
|
||||
: 'Verified — Rec Room Plus is now on your account.'
|
||||
)
|
||||
setRelogin(true)
|
||||
})
|
||||
.catch((err: unknown) => setError(err instanceof Error ? err.message : String(err)))
|
||||
.finally(() => setPending(false))
|
||||
}, [])
|
||||
|
||||
// Read into a local so the narrowing survives into the click handlers below.
|
||||
const authorizeUrl = config.discordAuthorizeUrl
|
||||
if (!config.benefitsEnabled || authorizeUrl === null) {
|
||||
return (
|
||||
<section className="card">
|
||||
<h2>Claim benefits</h2>
|
||||
<p className="muted">Benefit claims aren’t available on this server right now.</p>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const claimed = status?.hasPlus === true
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<h2>Rec Room Plus</h2>
|
||||
<p className="muted">
|
||||
Members of our Discord with a supporter role get Rec Room Plus on their account. Verify with
|
||||
Discord and we’ll check your roles — we only ever read your username and which roles you
|
||||
hold in our server.
|
||||
</p>
|
||||
<p className="muted">
|
||||
Claiming as <strong>@{account.username}</strong> (#{account.accountId}). A Discord account
|
||||
can claim on one RecFlare account only.
|
||||
</p>
|
||||
|
||||
{error && <p className="error">{error}</p>}
|
||||
{done && <p className="ok">{done}</p>}
|
||||
{relogin && (
|
||||
<p className="hint">
|
||||
Restart Rec Room and sign in again to pick it up — your game reads Rec Room Plus from the
|
||||
session it signed in with, so it won’t show until then.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{pending ? (
|
||||
<p className="muted">Checking your Discord roles…</p>
|
||||
) : claimed ? (
|
||||
// Already claimed. The button stays, because a player whose roles changed (or who
|
||||
// re-linked) can safely run it again — the claim is idempotent on their own
|
||||
// account — but it no longer reads as the thing to do.
|
||||
<>
|
||||
{!done && (
|
||||
<>
|
||||
<p className="ok">Rec Room Plus is active on this account.</p>
|
||||
<p className="hint">
|
||||
If the game doesn’t show it, sign out and back in — Rec Room Plus is read from the
|
||||
session your game signed in with.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<button className="linkish" onClick={() => startDiscordAuth(authorizeUrl)}>
|
||||
Re-verify with Discord
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="cta discord"
|
||||
onClick={() => startDiscordAuth(authorizeUrl)}
|
||||
>
|
||||
Verify with Discord
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* `/claim` — the page Discord redirects back to.
|
||||
*
|
||||
* Not linked from anywhere any more: the claim lives in the account dashboard's "Claim
|
||||
* benefits" tab. This route still has to exist and still has to work on a cold load,
|
||||
* because it is the app's registered `redirect_uri` — the browser arrives here from
|
||||
* Discord carrying the `?code=`, with whatever session it has.
|
||||
*
|
||||
* Signing in comes FIRST, and not only because the grant needs an account to land on: the
|
||||
* bearer token is what tells `www` whose row to write, so a claim without one has no
|
||||
* subject. Hence the sign-in card rather than a redirect — someone who arrives here from a
|
||||
* link should be told what this is before being bounced to a login form.
|
||||
*/
|
||||
function ClaimPage({
|
||||
account,
|
||||
config,
|
||||
navigate,
|
||||
}: {
|
||||
account: SelfAccount | null | undefined
|
||||
config: SiteConfig | undefined
|
||||
navigate: Navigate
|
||||
}) {
|
||||
if (account === undefined || config === undefined) {
|
||||
return (
|
||||
<main className="shell">
|
||||
<p className="muted">Loading…</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
if (account === null) {
|
||||
return (
|
||||
<main className="shell">
|
||||
<h1>Claim your benefits</h1>
|
||||
<section className="card">
|
||||
<h2>Sign in first</h2>
|
||||
<p className="muted">
|
||||
Benefits are granted to a RecFlare account, so we need to know which one is yours before
|
||||
you verify with Discord. If you were part-way through a claim, start it again from your
|
||||
account page once you’re signed in.
|
||||
</p>
|
||||
<Link to="/login" navigate={navigate} className="cta">
|
||||
Sign in
|
||||
</Link>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="shell">
|
||||
<h1>Claim your benefits</h1>
|
||||
<BenefitsPanel account={account} config={config} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The room id in `/rooms/<id>`, or null for any other path. Numeric rather than the
|
||||
* room's name: a name is renameable (`PUT /rooms/{id}/name`), so a link someone
|
||||
@@ -597,7 +897,12 @@ export function App() {
|
||||
.catch(() => setAccount(null))
|
||||
})
|
||||
.catch(() => {
|
||||
setConfig({ signupEnabled: false, turnstileSiteKey: null })
|
||||
setConfig({
|
||||
signupEnabled: false,
|
||||
turnstileSiteKey: null,
|
||||
benefitsEnabled: false,
|
||||
discordAuthorizeUrl: null,
|
||||
})
|
||||
setAccount(null)
|
||||
})
|
||||
}, [])
|
||||
@@ -627,7 +932,11 @@ export function App() {
|
||||
onAuthed={setAccount}
|
||||
/>
|
||||
) : path === '/account' ? (
|
||||
<AccountPage account={account} navigate={navigate} onChange={setAccount} />
|
||||
<AccountPage account={account} config={config} navigate={navigate} onChange={setAccount} />
|
||||
) : path === '/claim' ? (
|
||||
// Its own page rather than a dashboard tab: this path is Discord's registered
|
||||
// redirect URI, so it has to be one stable URL a cold load can land on.
|
||||
<ClaimPage account={account} config={config} navigate={navigate} />
|
||||
) : roomId !== null ? (
|
||||
<RoomPage account={account} roomId={roomId} navigate={navigate} />
|
||||
) : (
|
||||
@@ -818,7 +1127,7 @@ function Stage({
|
||||
trademark stays out of the headline and appears lower down, in
|
||||
plain nominative use next to the disclaimer. */}
|
||||
<h1 className="stage-title">
|
||||
Play like it's <em>2023</em>.
|
||||
Play <em>today</em>!
|
||||
</h1>
|
||||
<p className="stage-lede">
|
||||
The servers you remember, rebuilt and running — free, open source, and up right now.
|
||||
@@ -913,7 +1222,7 @@ function About({ slides, error }: { slides: Slide[] | null; error: string }) {
|
||||
return (
|
||||
<section className="about">
|
||||
<div>
|
||||
<h2 className="about-title">An open source rebuild of the 2023 servers</h2>
|
||||
<h2 className="about-title">A cloud architected server for the 2023/2025 game clients</h2>
|
||||
<p className="about-lede">
|
||||
A free fan project, made by players who missed it. Aiming to be{' '}
|
||||
<strong>feature-complete</strong> and infinitely scalable —{' '}
|
||||
@@ -1030,10 +1339,12 @@ function LoginPage({
|
||||
/** The signed-in account page. Redirects to sign-in when there's no session. */
|
||||
function AccountPage({
|
||||
account,
|
||||
config,
|
||||
navigate,
|
||||
onChange,
|
||||
}: {
|
||||
account: SelfAccount | null | undefined
|
||||
config: SiteConfig | undefined
|
||||
navigate: Navigate
|
||||
onChange: (a: SelfAccount) => void
|
||||
}) {
|
||||
@@ -1052,7 +1363,7 @@ function AccountPage({
|
||||
return (
|
||||
<main className="shell wide">
|
||||
<h1>My account</h1>
|
||||
<Dashboard account={account} navigate={navigate} onChange={onChange} />
|
||||
<Dashboard account={account} config={config} navigate={navigate} onChange={onChange} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1382,10 +1693,10 @@ function BlobUpload({
|
||||
<span className="badge beta">Beta</span>
|
||||
</p>
|
||||
<p className="muted blob-upload-caveat">
|
||||
New and lightly tested. Nothing here checks the file — the server stores whatever it
|
||||
is and the game finds out on load. This server runs the {CLIENT_BUILD_DATE} build, so
|
||||
scene data from a room built on anything newer may not load at all. Download the save
|
||||
above and keep it before replacing it.
|
||||
New and lightly tested. Nothing here checks the file — the server stores whatever it is and
|
||||
the game finds out on load. This server runs the {CLIENT_BUILD_DATE} build, so scene data
|
||||
from a room built on anything newer may not load at all. Download the save above and keep it
|
||||
before replacing it.
|
||||
</p>
|
||||
<label className="blob-upload-file">
|
||||
Scene data file
|
||||
@@ -1776,10 +2087,12 @@ function LoginForm({ onAuthed }: { onAuthed: (a: SelfAccount) => void }) {
|
||||
|
||||
function Dashboard({
|
||||
account,
|
||||
config,
|
||||
navigate,
|
||||
onChange,
|
||||
}: {
|
||||
account: SelfAccount
|
||||
config: SiteConfig | undefined
|
||||
navigate: Navigate
|
||||
onChange: (a: SelfAccount) => void
|
||||
}) {
|
||||
@@ -1800,10 +2113,22 @@ function Dashboard({
|
||||
render: () => <EmailForm account={account} onChange={onChange} />,
|
||||
},
|
||||
{ id: 'password', label: 'Password', render: () => <PasswordForm /> },
|
||||
// Only when the operator has Discord configured — otherwise the panel has nothing to
|
||||
// offer and the tab is a promise the server can't keep. The claim also still lives at
|
||||
// /claim, because that URL is Discord's registered redirect and has to keep working.
|
||||
...(config?.benefitsEnabled
|
||||
? [
|
||||
{
|
||||
id: 'benefits',
|
||||
label: 'Claim benefits',
|
||||
render: () => <BenefitsPanel account={account} config={config} />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isAdmin()
|
||||
? [
|
||||
{ id: 'maintenance', label: 'Server maintenance', render: () => <MaintenanceForm /> },
|
||||
{ id: 'coach', label: 'Broadcast message', render: () => <CoachMessageForm /> },
|
||||
{ id: 'coach', label: 'Coach message', render: () => <CoachMessageForm /> },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
@@ -1938,28 +2263,61 @@ function RoomCard({
|
||||
)
|
||||
}
|
||||
|
||||
/** Admin-only: send a coach/system message to every online player. */
|
||||
/**
|
||||
* Admin-only: send a coach/system message, either to one player by `@username` or to
|
||||
* everyone online.
|
||||
*
|
||||
* The two go to different endpoints because they behave differently, not just in reach:
|
||||
* the broadcast is online-only (nothing holds a message with no addressee), while a named
|
||||
* recipient's message is queued by the hub and delivered whenever they next connect. The
|
||||
* recipient box therefore says which of those the operator is about to do.
|
||||
*/
|
||||
function CoachMessageForm() {
|
||||
const [recipient, setRecipient] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
const { pending, error, done, run } = useAction()
|
||||
// The `@` is how the name is written, not part of it — accepted either way, shown back
|
||||
// with it, and sent without it.
|
||||
const handle = recipient.trim().replace(/^@/, '')
|
||||
const toOne = handle !== ''
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<h2>Broadcast message</h2>
|
||||
<h2>Coach message</h2>
|
||||
<p className="muted">
|
||||
Send a message from the Coach to every connected player. Players who aren't online
|
||||
won't receive it.
|
||||
Send a message from the Coach to one player, or leave the recipient blank to send it to
|
||||
every connected player. A broadcast reaches only who is online right now; a message to one
|
||||
player waits for them if they aren't.
|
||||
</p>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
void run(async () => {
|
||||
const { sent } = await coachMessageAll(message.trim())
|
||||
const content = message.trim()
|
||||
if (!toOne) {
|
||||
const { sent } = await coachMessageAll(content)
|
||||
setMessage('')
|
||||
return `Sent to ${sent ?? 0} online player${sent === 1 ? '' : 's'}.`
|
||||
}
|
||||
// Resolved before sending: the workers address players by id, and a name that
|
||||
// matches nobody should be a refusal rather than a message into the void.
|
||||
const { queued } = await coachMessage(await accountIdForUsername(handle), content)
|
||||
setMessage('')
|
||||
return `Sent to ${sent ?? 0} online player${sent === 1 ? '' : 's'}.`
|
||||
return queued === true
|
||||
? `@${handle} is offline — it will arrive when they next connect.`
|
||||
: `Sent to @${handle}.`
|
||||
})
|
||||
}}
|
||||
>
|
||||
<label>
|
||||
Send to
|
||||
<input
|
||||
value={recipient}
|
||||
placeholder="@username — blank sends to everyone online"
|
||||
autoComplete="off"
|
||||
onChange={(e) => setRecipient(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Message
|
||||
<textarea
|
||||
@@ -1972,7 +2330,7 @@ function CoachMessageForm() {
|
||||
{error && <p className="error">{error}</p>}
|
||||
{done && <p className="ok">{done}</p>}
|
||||
<button type="submit" disabled={pending}>
|
||||
{pending ? 'Sending…' : 'Send to all online'}
|
||||
{pending ? 'Sending…' : toOne ? `Send to @${handle}` : 'Send to all online'}
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -222,7 +222,8 @@ body {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* The one place the orange carries meaning in the headline: the year it restores. */
|
||||
/* The one accented word in the headline — it carries the whole emphasis, so the markup
|
||||
always wraps exactly one word in <em>. */
|
||||
.stage-title em {
|
||||
font-style: normal;
|
||||
color: var(--accent);
|
||||
|
||||
+47
-3
@@ -7,9 +7,11 @@ export type Env = SharedHonoEnv & {
|
||||
/** Static-asset fetcher for the built React SPA (see wrangler.jsonc `assets`). */
|
||||
ASSETS: Fetcher
|
||||
/**
|
||||
* The shared `recflare` D1, bound READ-ONLY in practice: the only thing www asks it
|
||||
* is the live presence head-count behind `/server-status`. Every table it can see is
|
||||
* owned (and migrated) by another worker.
|
||||
* The shared `recflare` D1. www asks it two things: the live presence head-count
|
||||
* behind `/server-status`, and the caller's `account` row on the benefits claim —
|
||||
* which is also the one place www WRITES (the `hasPlus`/`discordUserId` pair, through
|
||||
* `@repo/domain`'s `updateAccount`, so the blob's shape stays in one module). Every
|
||||
* table it can see is owned (and migrated) by another worker; www never migrates.
|
||||
*/
|
||||
DB: D1Database
|
||||
/**
|
||||
@@ -39,6 +41,48 @@ export type Env = SharedHonoEnv & {
|
||||
* failing to resolve closes web signup — see src/turnstile.ts.
|
||||
*/
|
||||
TURNSTILE_SECRET_KEY: SecretsStoreSecret
|
||||
/**
|
||||
* The HS256 signing key every worker shares, out of the same account-level Secrets
|
||||
* Store. www needs it for ONE thing: the benefits claim is the only route here that
|
||||
* acts on behalf of a specific account (it writes `hasPlus` onto it), so it has to
|
||||
* establish WHICH account is calling rather than take the SPA's word for it. Every
|
||||
* other www route is either anonymous or hands the token straight to another worker.
|
||||
*
|
||||
* Resolve with `await env.JWT_SECRET.get()`; validate through `@repo/jwt` so the
|
||||
* signature/exp checks are the ones every other worker runs.
|
||||
*/
|
||||
JWT_SECRET: SecretsStoreSecret
|
||||
/**
|
||||
* The Discord application's client id. PUBLIC — it ships to the browser, which needs
|
||||
* it to build the authorize URL — but kept in the Secrets Store beside its secret so
|
||||
* one place configures the claim, exactly as TURNSTILE_SITE_KEY is.
|
||||
*/
|
||||
DISCORD_CLIENT_ID: SecretsStoreSecret
|
||||
/**
|
||||
* The Discord application's client secret — what turns an authorization code into an
|
||||
* access token. Never leaves this worker (see src/discord.ts).
|
||||
*/
|
||||
DISCORD_CLIENT_SECRET: SecretsStoreSecret
|
||||
/**
|
||||
* The guild (Discord server) whose membership the benefits claim checks, and the roles
|
||||
* within it that entitle a player to the benefits. Both hold Discord SNOWFLAKES — all
|
||||
* digits, never a role's display name — kept as strings because a snowflake exceeds
|
||||
* 2^53. Plain vars rather than secrets: any member of the server can read these off
|
||||
* their own client, and none of them authorizes anything on its own.
|
||||
*
|
||||
* OPTIONAL because an operator who hasn't set up Discord has neither, and that is a
|
||||
* supported state: it CLOSES the claim (see `discordConfig`) rather than opening an
|
||||
* unverified one.
|
||||
*/
|
||||
DISCORD_GUILD_ID?: string
|
||||
/**
|
||||
* The role ids inside DISCORD_GUILD_ID that grant Rec Room Plus — numeric snowflakes,
|
||||
* separated by commas and/or whitespace, e.g. `"1077000000000000001,1077000000000000002"`.
|
||||
* ANY one of them qualifies, so several tiers (a supporter role, a booster role, staff)
|
||||
* can share the same benefit. Parsed by `parseRoleIds`; a value that parses to no ids at
|
||||
* all closes the claim, exactly as an unset one does.
|
||||
*/
|
||||
DISCORD_BENEFITS_ROLE_IDS?: string
|
||||
}
|
||||
|
||||
/** Variables can be extended */
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
import { logger } from '@repo/hono-helpers'
|
||||
|
||||
import type { Env } from './context'
|
||||
|
||||
/**
|
||||
* Discord OAuth2, the identity check behind the website's benefits claim.
|
||||
*
|
||||
* A player proves they hold one of the qualifying roles in the community Discord, and
|
||||
* the claim grants them Rec Room Plus (`account.hasPlus`). The proof is a real OAuth2
|
||||
* AUTHORIZATION CODE exchange, not a token the browser hands us: the SPA sends only the
|
||||
* short-lived `code` Discord redirected it back with, and this worker swaps that for an
|
||||
* access token using the client SECRET, which — like the Turnstile secret next door —
|
||||
* can never ship to a page. The access token therefore never exists in the browser at
|
||||
* all, and it is revoked here the moment the roles have been read.
|
||||
*
|
||||
* Roles come from `GET /users/@me/guilds/{guild}/member`, which needs no bot: the
|
||||
* `guilds.members.read` scope lets the TOKEN'S OWNER read their own membership. That is
|
||||
* the whole reason this shape was chosen over a bot token — nothing here has to be in
|
||||
* the guild, and the worker holds no credential that could read anybody else's roles.
|
||||
*
|
||||
* The four settings (client id, client secret, guild, one or more roles) are the switch,
|
||||
* exactly as the Turnstile keypair is for signup: with any of them missing the claim is CLOSED
|
||||
* (`/api/config` says so and `/api/benefits/claim` refuses) rather than open and
|
||||
* unverified. Nothing is ever inferred from the environment.
|
||||
*/
|
||||
|
||||
/** Discord's API, pinned to v10 — the version the endpoints below are documented at. */
|
||||
const API_BASE = 'https://discord.com/api/v10'
|
||||
|
||||
/**
|
||||
* Where the browser is sent to consent. Deliberately NOT under `/api/v10`: the authorize
|
||||
* page is a human-facing page on the main site, and the versioned path serves a redirect
|
||||
* to it at best.
|
||||
*/
|
||||
export const AUTHORIZE_URL = 'https://discord.com/oauth2/authorize'
|
||||
|
||||
/**
|
||||
* The scopes the claim asks for, in the order Discord shows them on the consent screen.
|
||||
*
|
||||
* - `identify` — the user's own id, which the claim stores as a `PlatformType.Discord`
|
||||
* link on the account to keep itself once-only.
|
||||
* - `guilds.members.read` — their member record (and so their ROLES) in one guild they
|
||||
* are in. Narrower than `guilds`, which lists every server they belong to and is not
|
||||
* needed: the claim asks about exactly one guild.
|
||||
*
|
||||
* A space-joined string because that is how the authorize URL takes them.
|
||||
*/
|
||||
export const SCOPES = 'identify guilds.members.read'
|
||||
|
||||
/** Everything the claim needs configured. Resolved per request; see `discordConfig`. */
|
||||
export interface DiscordConfig {
|
||||
/** The application's client id. PUBLIC — it ships to the browser in the authorize URL. */
|
||||
clientId: string
|
||||
/** The application's client secret. Never leaves this worker. */
|
||||
clientSecret: string
|
||||
/** The guild (server) whose membership is checked. */
|
||||
guildId: string
|
||||
/**
|
||||
* The role ids within that guild that entitle a player to the benefits — Discord
|
||||
* snowflakes, all digits. ANY one of them qualifies: they're alternatives (a supporter
|
||||
* role, a booster role, staff…), not requirements, so this is a set to test membership
|
||||
* against and never an ordered list. Always at least one entry — an empty list closes
|
||||
* the claim (see `discordConfig`).
|
||||
*/
|
||||
roleIds: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the configured role ids — Discord snowflakes, so each one is ALL DIGITS (a role's
|
||||
* display name is not an id and will never match anything). They stay strings rather than
|
||||
* becoming numbers: a snowflake exceeds 2^53, and they are only ever compared, never done
|
||||
* arithmetic on.
|
||||
*
|
||||
* Separated by commas and/or whitespace, so a value pasted out of Discord one id per line
|
||||
* works as well as `1077000000000000001,1077000000000000002` does; blank entries are
|
||||
* dropped, which is what makes a trailing comma harmless rather than a role id of `''`
|
||||
* that nothing can ever match.
|
||||
*
|
||||
* The digits are not ENFORCED here, deliberately. A typo'd snowflake is indistinguishable
|
||||
* from a real role nobody holds, and both correctly result in a claim being refused, so a
|
||||
* format rule would buy nothing but a way to reject a valid id if Discord ever widens the
|
||||
* format. Misconfiguration shows up as "nobody can claim", which is the safe direction.
|
||||
*/
|
||||
export const parseRoleIds = (raw: string): string[] =>
|
||||
raw
|
||||
.split(/[\s,]+/)
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id !== '')
|
||||
|
||||
/**
|
||||
* The Discord settings, or null when the claim isn't configured — which is what CLOSES
|
||||
* it. All four must be present, and the role list must parse to at least ONE id: a client
|
||||
* id with no roles would authenticate a player and then have no question to ask about
|
||||
* them, and treating that as "configured" would hand Plus to anyone with a Discord
|
||||
* account.
|
||||
*
|
||||
* Which of the four is missing is logged (never their values) because a half-configured
|
||||
* app is otherwise indistinguishable from an operator deliberately leaving benefits off.
|
||||
*
|
||||
* The id and secret come from the account-level Secrets Store the whole monorepo shares,
|
||||
* so they're read per request rather than off `env` as strings; `.get()` caches per
|
||||
* isolate, so changing either needs a `www` redeploy to take effect on a warm worker —
|
||||
* the same caveat TURNSTILE_* and JWT_SECRET carry. The guild and roles are plain vars:
|
||||
* they're server ids visible to every member, not credentials.
|
||||
*/
|
||||
export async function discordConfig(env: Env): Promise<DiscordConfig | null> {
|
||||
const [clientId, clientSecret] = await Promise.all([
|
||||
readSecret(env.DISCORD_CLIENT_ID, 'DISCORD_CLIENT_ID'),
|
||||
readSecret(env.DISCORD_CLIENT_SECRET, 'DISCORD_CLIENT_SECRET'),
|
||||
])
|
||||
const guildId = env.DISCORD_GUILD_ID ?? ''
|
||||
const roleIds = parseRoleIds(env.DISCORD_BENEFITS_ROLE_IDS ?? '')
|
||||
|
||||
if (clientId !== '' && clientSecret !== '' && guildId !== '' && roleIds.length > 0) {
|
||||
return { clientId, clientSecret, guildId, roleIds }
|
||||
}
|
||||
if (clientId !== '' || clientSecret !== '' || guildId !== '' || roleIds.length > 0) {
|
||||
logger.error('discord is half-configured, so benefit claims are closed', {
|
||||
hasClientId: clientId !== '',
|
||||
hasClientSecret: clientSecret !== '',
|
||||
hasGuildId: guildId !== '',
|
||||
// The COUNT, not the ids: a value that parsed to nothing (say, a stray comma) is
|
||||
// indistinguishable from an unset one without it.
|
||||
roleIdCount: roleIds.length,
|
||||
})
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* One Secrets Store value as a string, or '' when it can't be read. The binding is
|
||||
* declared in wrangler.jsonc so it's always on `env`; what varies is whether the store
|
||||
* holds the secret — a missing one throws rather than resolving empty. Mirrors
|
||||
* `turnstile.ts`'s reader, and for the same reason: a store this worker can't read must
|
||||
* close the feature, not 500 the homepage.
|
||||
*/
|
||||
async function readSecret(secret: SecretsStoreSecret, name: string): Promise<string> {
|
||||
try {
|
||||
return (await secret.get()) ?? ''
|
||||
} catch (err) {
|
||||
logger.error('failed to read a discord credential from the secrets store', {
|
||||
secret: name,
|
||||
error: String(err),
|
||||
})
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The URI Discord redirects back to after consent, derived from the request rather than
|
||||
* configured.
|
||||
*
|
||||
* It must be byte-identical in three places — the authorize URL the browser opens, the
|
||||
* token exchange below, and the app's registered redirect list — or Discord refuses the
|
||||
* exchange. Deriving it from the incoming request's own origin is what keeps the first
|
||||
* two in step across every environment (localhost in dev, the real domain in
|
||||
* production) with nothing to configure, and it is also why the SPA does NOT get to
|
||||
* supply it in the request body: an attacker-supplied redirect would turn this worker's
|
||||
* client secret into a redemption oracle for codes issued to somebody else's app page.
|
||||
*
|
||||
* `/claim` is the SPA route that handles the return; see App.tsx.
|
||||
*/
|
||||
export const redirectUri = (request: Request): string => new URL('/claim', request.url).toString()
|
||||
|
||||
/**
|
||||
* Swap an authorization code for an access token. Returns null on any refusal — a code
|
||||
* that was already spent, expired (they live ~1 minute), issued to another app, or paired
|
||||
* with a different redirect URI all land here, and none of them is worth telling the
|
||||
* browser apart: the answer is the same, start the flow again.
|
||||
*
|
||||
* The credentials go in the BODY rather than a Basic auth header. Both are legal and
|
||||
* Discord documents the body form.
|
||||
*/
|
||||
export async function exchangeCode(
|
||||
config: DiscordConfig,
|
||||
code: string,
|
||||
redirect: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/oauth2/token`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
client_id: config.clientId,
|
||||
client_secret: config.clientSecret,
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
redirect_uri: redirect,
|
||||
}).toString(),
|
||||
})
|
||||
if (!res.ok) {
|
||||
// The body carries an OAuth error code (`invalid_grant`, `invalid_client`) — the
|
||||
// last of which is a misconfiguration, not a player mistake, and this line is the
|
||||
// only place it surfaces. Logged, never relayed: it tells a caller nothing.
|
||||
logger.info('discord refused a code exchange', {
|
||||
status: res.status,
|
||||
body: await res.text().catch(() => ''),
|
||||
})
|
||||
return null
|
||||
}
|
||||
const token = (await res.json()) as { access_token?: unknown }
|
||||
return typeof token.access_token === 'string' ? token.access_token : null
|
||||
} catch (err) {
|
||||
logger.error('could not reach discord to exchange a code', { error: String(err) })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a member holds ANY of the qualifying roles. Both sides are snowflake id
|
||||
* strings, compared exactly — Discord reports a member's roles as ids, never as names.
|
||||
*
|
||||
* The roles are alternatives (a supporter, a booster and a staff member all qualify), so
|
||||
* this is an intersection test and not a subset one: requiring all of them would mean
|
||||
* nobody ever claimed.
|
||||
*/
|
||||
export const qualifies = (memberRoles: string[], roleIds: string[]): boolean =>
|
||||
memberRoles.some((role) => roleIds.includes(role))
|
||||
|
||||
/** Who claimed, and what they hold in the guild. */
|
||||
export interface GuildMembership {
|
||||
/** The Discord user's id (a snowflake, kept as a string — it exceeds 2^53). */
|
||||
userId: string
|
||||
/** Their Discord username, for the confirmation line. Display only, never stored. */
|
||||
username: string
|
||||
/** Their role ids in the guild. */
|
||||
roles: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The token owner's membership in the configured guild, or null when they aren't in it
|
||||
* (Discord answers 404) or the call fails.
|
||||
*
|
||||
* `null` deliberately conflates "not a member" with "we couldn't ask". Both mean the same
|
||||
* thing to the claim — no proof was obtained — and a claim that granted benefits when
|
||||
* Discord was unreachable would be worse than one that asks the player to retry.
|
||||
*/
|
||||
export async function fetchGuildMembership(
|
||||
accessToken: string,
|
||||
guildId: string
|
||||
): Promise<GuildMembership | null> {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/users/@me/guilds/${guildId}/member`, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
if (!res.ok) {
|
||||
// 404 is the ordinary "they aren't in the server" answer, so it's info, not error.
|
||||
logger.info('discord did not return a guild membership', { status: res.status })
|
||||
return null
|
||||
}
|
||||
const member = (await res.json()) as {
|
||||
user?: { id?: unknown; username?: unknown }
|
||||
roles?: unknown
|
||||
}
|
||||
const userId = typeof member.user?.id === 'string' ? member.user.id : ''
|
||||
if (userId === '') {
|
||||
logger.error('discord returned a guild member with no user id')
|
||||
return null
|
||||
}
|
||||
return {
|
||||
userId,
|
||||
username: typeof member.user?.username === 'string' ? member.user.username : '',
|
||||
roles: Array.isArray(member.roles)
|
||||
? member.roles.filter((r): r is string => typeof r === 'string')
|
||||
: [],
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('could not reach discord to read a guild membership', { error: String(err) })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hand the access token back to Discord once the roles have been read.
|
||||
*
|
||||
* Best-effort and deliberately un-awaited-on by the caller's success path: the claim has
|
||||
* already been decided by this point, so a failed revoke must not fail it. It's here
|
||||
* because the token is useless to us after one read and a live token is a liability for
|
||||
* however long it would otherwise last (a week) — this keeps the credential's lifetime
|
||||
* about as long as the request that needed it.
|
||||
*/
|
||||
export async function revokeToken(config: DiscordConfig, accessToken: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`${API_BASE}/oauth2/token/revoke`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
client_id: config.clientId,
|
||||
client_secret: config.clientSecret,
|
||||
token: accessToken,
|
||||
token_type_hint: 'access_token',
|
||||
}).toString(),
|
||||
})
|
||||
} catch (err) {
|
||||
logger.info('could not revoke a discord access token', { error: String(err) })
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,19 @@
|
||||
import { adminSecretsStore, env, SELF } from 'cloudflare:test'
|
||||
import { beforeAll, expect, it } from 'vitest'
|
||||
|
||||
import { SCHEMA_DDL as ACCOUNT_SCHEMA_DDL, updateAccount } from '@repo/domain/src/accounts-db'
|
||||
import { PlatformType } from '@repo/domain/src/enums'
|
||||
import { PRESENCE_SCHEMA_DDL, PRESENCE_TTL_SECONDS } from '@repo/domain/src/presence-db'
|
||||
import { generateToken } from '@repo/jwt'
|
||||
|
||||
import {
|
||||
CACHED_LOGIN_PLATFORMS,
|
||||
countAccountsForPlatformIdentity,
|
||||
isPlatformIdentityLinked,
|
||||
linkPlatformIdentity,
|
||||
PLATFORM_SCHEMA_DDL,
|
||||
} from '../../../../auth/src/platform-db'
|
||||
import { discordConfig, parseRoleIds, qualifies } from '../../discord'
|
||||
import { DOCUMENTED_SERVICES } from '../../docs'
|
||||
import { DISCORD_INVITE, ISSUES_URL, PRIVACY_EMAIL } from '../../links'
|
||||
import { turnstileKeys } from '../../turnstile'
|
||||
@@ -21,12 +32,37 @@ declare module 'cloudflare:test' {
|
||||
const TEST_SITE_KEY = '1x00000000000000000000AA'
|
||||
const TEST_SECRET_KEY = '1x0000000000000000000000000000000AA'
|
||||
|
||||
// The shared HS256 key. www verifies tokens itself for exactly one route (the benefits
|
||||
// claim), so the tests have to be able to MINT one — hence a known value here rather than
|
||||
// whatever a deployed store holds.
|
||||
const TEST_JWT_SECRET = 'test-jwt-secret'
|
||||
|
||||
/** A bearer token for `accountId`, signed the way `auth` signs one. */
|
||||
const tokenFor = (accountId: number): Promise<string> =>
|
||||
generateToken(String(accountId), '', 4, TEST_JWT_SECRET)
|
||||
|
||||
// A Discord app that is HALF configured: credentials seeded below, but wrangler.jsonc
|
||||
// leaves DISCORD_GUILD_ID / DISCORD_BENEFITS_ROLE_IDS empty. This is deliberately the most
|
||||
// dangerous half — an operator who registers an app and stops has something that can sign
|
||||
// a player in and no question left to ask about them — so it is the state the route-level
|
||||
// tests pin: the claim must still be CLOSED. The fully-configured path is covered by
|
||||
// unit-testing `discordConfig`, since exercising it end to end would call discord.com.
|
||||
const TEST_DISCORD_CLIENT_ID = 'test-discord-client-id'
|
||||
const TEST_DISCORD_CLIENT_SECRET = 'test-discord-client-secret'
|
||||
|
||||
beforeAll(async () => {
|
||||
await adminSecretsStore(env.TURNSTILE_SITE_KEY).create(TEST_SITE_KEY)
|
||||
await adminSecretsStore(env.TURNSTILE_SECRET_KEY).create(TEST_SECRET_KEY)
|
||||
await adminSecretsStore(env.JWT_SECRET).create(TEST_JWT_SECRET)
|
||||
await adminSecretsStore(env.DISCORD_CLIENT_ID).create(TEST_DISCORD_CLIENT_ID)
|
||||
await adminSecretsStore(env.DISCORD_CLIENT_SECRET).create(TEST_DISCORD_CLIENT_SECRET)
|
||||
// `presence` is owned (and migrated) by other workers — www only reads it — so the
|
||||
// table has to be created here for the head-count behind /server-status.
|
||||
for (const stmt of PRESENCE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
// `account` likewise: owned by `auth`, read and (for the benefits claim) written here.
|
||||
for (const stmt of ACCOUNT_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
// And `platform_account`, where a claimed Discord identity is linked.
|
||||
for (const stmt of PLATFORM_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
})
|
||||
|
||||
// Web signup is open, but only behind the Turnstile check. These pin the closed door:
|
||||
@@ -42,6 +78,11 @@ it('advertises signup and where the other workers live', async () => {
|
||||
expect(await res.json()).toEqual({
|
||||
signupEnabled: true,
|
||||
turnstileSiteKey: TEST_SITE_KEY,
|
||||
// Closed, because the Discord app here has no guild/role to check against — and with
|
||||
// it closed the SPA is given no authorize URL to send anyone to, so the claim can't
|
||||
// even be started. Note the client id is NOT leaked by a closed config.
|
||||
benefitsEnabled: false,
|
||||
discordAuthorizeUrl: null,
|
||||
hosts: {
|
||||
auth: 'https://auth.rec.example.com',
|
||||
accounts: 'https://accounts.rec.example.com',
|
||||
@@ -281,6 +322,220 @@ it('404s a spec proxy for an unknown service (not an open proxy)', async () => {
|
||||
expect(res.status).toBe(404)
|
||||
})
|
||||
|
||||
// ---- Discord benefits claim ------------------------------------------------
|
||||
|
||||
// All four settings are the switch, exactly as the Turnstile keypair is for signup: a
|
||||
// half-configured app must read as OFF. The dangerous half is a client id and secret with
|
||||
// no guild/role — that authenticates a player and then has no question left to ask about
|
||||
// them, so treating it as configured would hand Rec Room Plus to anyone with a Discord
|
||||
// account. Checked directly because the configured path can't be reached from here (it
|
||||
// would call discord.com for real).
|
||||
it('treats a half-configured discord app as benefit claims being off', async () => {
|
||||
const stub = (value: string | null): SecretsStoreSecret =>
|
||||
({ get: async () => value ?? '' }) as SecretsStoreSecret
|
||||
const throws = (): SecretsStoreSecret =>
|
||||
({
|
||||
get: async () => {
|
||||
throw new Error('secret not found')
|
||||
},
|
||||
}) as unknown as SecretsStoreSecret
|
||||
|
||||
const withDiscord = (
|
||||
id: SecretsStoreSecret,
|
||||
secret: SecretsStoreSecret,
|
||||
guildId?: string,
|
||||
roleIds?: string
|
||||
) =>
|
||||
({
|
||||
ENVIRONMENT: 'development',
|
||||
DISCORD_CLIENT_ID: id,
|
||||
DISCORD_CLIENT_SECRET: secret,
|
||||
DISCORD_GUILD_ID: guildId,
|
||||
DISCORD_BENEFITS_ROLE_IDS: roleIds,
|
||||
}) as Env
|
||||
|
||||
const id = stub('client-id')
|
||||
const secret = stub('client-secret')
|
||||
// Snowflakes, as the real vars hold: ids are all digits, never a role's display name.
|
||||
const guild = '1077000000000000000'
|
||||
const role = '1077000000000000001'
|
||||
|
||||
// Nothing at all, and a store this worker can't read: both closed, never a 500.
|
||||
await expect(discordConfig(withDiscord(throws(), throws()))).resolves.toBeNull()
|
||||
await expect(discordConfig(withDiscord(stub(''), stub(''), '', ''))).resolves.toBeNull()
|
||||
// Each single missing piece, including the two that would otherwise grant Plus for a
|
||||
// bare Discord login.
|
||||
await expect(discordConfig(withDiscord(throws(), secret, guild, role))).resolves.toBeNull()
|
||||
await expect(discordConfig(withDiscord(id, throws(), guild, role))).resolves.toBeNull()
|
||||
await expect(discordConfig(withDiscord(id, secret, '', role))).resolves.toBeNull()
|
||||
await expect(discordConfig(withDiscord(id, secret, guild, ''))).resolves.toBeNull()
|
||||
await expect(discordConfig(withDiscord(id, secret))).resolves.toBeNull()
|
||||
// A role list that parses to NO ids is unset, not configured — otherwise a stray comma
|
||||
// left in the var would open the claim with nothing to check against.
|
||||
await expect(discordConfig(withDiscord(id, secret, guild, ' , , '))).resolves.toBeNull()
|
||||
// All four present is the only configured state.
|
||||
await expect(discordConfig(withDiscord(id, secret, guild, role))).resolves.toEqual({
|
||||
clientId: 'client-id',
|
||||
clientSecret: 'client-secret',
|
||||
guildId: guild,
|
||||
roleIds: [role],
|
||||
})
|
||||
// Several qualifying roles is the ordinary case, not a special one.
|
||||
const second = '1077000000000000002'
|
||||
await expect(discordConfig(withDiscord(id, secret, guild, `${role},${second}`))).resolves.toEqual(
|
||||
{
|
||||
clientId: 'client-id',
|
||||
clientSecret: 'client-secret',
|
||||
guildId: guild,
|
||||
roleIds: [role, second],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// Several roles can qualify for the same benefit (a supporter role, a booster role,
|
||||
// staff…), so the list is parsed leniently: an operator pasting ids out of Discord gets
|
||||
// one per line, and a trailing comma is a typo rather than a role of '' that nothing
|
||||
// could ever match. Every id is a snowflake — all digits, kept as a string.
|
||||
it('parses a qualifying-role list however an operator writes it', () => {
|
||||
expect(parseRoleIds('1077000000000000001')).toEqual(['1077000000000000001'])
|
||||
expect(parseRoleIds('1077000000000000001,1077000000000000002')).toEqual([
|
||||
'1077000000000000001',
|
||||
'1077000000000000002',
|
||||
])
|
||||
expect(parseRoleIds(' 1077000000000000001 , 1077000000000000002 ')).toEqual([
|
||||
'1077000000000000001',
|
||||
'1077000000000000002',
|
||||
])
|
||||
// Pasted a line at a time, straight out of Discord.
|
||||
expect(parseRoleIds('1077000000000000001\n1077000000000000002\n')).toEqual([
|
||||
'1077000000000000001',
|
||||
'1077000000000000002',
|
||||
])
|
||||
// Kept as STRINGS, never parsed to numbers: a snowflake exceeds 2^53, so
|
||||
// Number('1077000000000000001') would round and stop matching the real role.
|
||||
expect(parseRoleIds('1077000000000000001')[0]).toBe('1077000000000000001')
|
||||
// Nothing to match on — these are the values that must close the claim.
|
||||
expect(parseRoleIds('')).toEqual([])
|
||||
expect(parseRoleIds(' ')).toEqual([])
|
||||
expect(parseRoleIds(',,')).toEqual([])
|
||||
// A trailing separator adds no empty id, which would match no role and never qualify.
|
||||
expect(parseRoleIds('1077000000000000001,')).toEqual(['1077000000000000001'])
|
||||
})
|
||||
|
||||
// ANY one of the configured roles qualifies — they are alternatives, not requirements.
|
||||
// Testing for a subset instead would mean a player had to hold every tier at once, i.e.
|
||||
// nobody would ever claim.
|
||||
it('qualifies a member holding any one of the roles', () => {
|
||||
// Ids on both sides — Discord reports a member's roles as snowflakes, never as names.
|
||||
const supporter = '1077000000000000001'
|
||||
const booster = '1077000000000000002'
|
||||
const qualifying = [supporter, booster]
|
||||
|
||||
expect(qualifies([supporter], qualifying)).toBe(true)
|
||||
expect(qualifies([booster], qualifying)).toBe(true)
|
||||
expect(qualifies([booster, supporter], qualifying)).toBe(true)
|
||||
// Holding some other role in the server is not enough.
|
||||
expect(qualifies(['1077000000000000009'], qualifying)).toBe(false)
|
||||
expect(qualifies([], qualifying)).toBe(false)
|
||||
})
|
||||
|
||||
// The closed door, from the outside. This must be refused BEFORE the token is looked at,
|
||||
// so an unconfigured server can't be talked into a claim by a valid session.
|
||||
it('refuses a benefits claim when discord is only half configured', async () => {
|
||||
const res = await SELF.fetch('https://example.com/api/benefits/claim', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
authorization: `Bearer ${await tokenFor(4001)}`,
|
||||
},
|
||||
body: JSON.stringify({ code: 'whatever' }),
|
||||
})
|
||||
expect(res.status).toBe(403)
|
||||
expect(await res.json()).toEqual({ error: 'Benefit claims are currently disabled.' })
|
||||
})
|
||||
|
||||
// The benefits routes act on ONE account — the claim writes `hasPlus` onto its row — so
|
||||
// which account it is has to come from a verified token and never from the request. Both
|
||||
// halves of "verified" are pinned: no token, and a token signed with a key this server
|
||||
// doesn't use (i.e. one it never issued).
|
||||
//
|
||||
// Asserted on `/api/benefits/status` because it's the benefits route whose auth gate is
|
||||
// reachable here: the claim refuses on the config gate FIRST (covered above), which is
|
||||
// the right order — an unconfigured server shouldn't be examining credentials for a
|
||||
// feature it doesn't run — but it means an unconfigured project can't observe its 401.
|
||||
it('requires a valid session to read benefits', async () => {
|
||||
const path = 'https://example.com/api/benefits/status'
|
||||
|
||||
// No token at all.
|
||||
expect((await SELF.fetch(path)).status).toBe(401)
|
||||
|
||||
// A token that is well-formed but signed with the wrong key.
|
||||
const forged = await generateToken('4002', '', 4, 'not-the-real-secret')
|
||||
const res = await SELF.fetch(path, { headers: { authorization: `Bearer ${forged}` } })
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
|
||||
// What the claim page renders before anyone presses anything. `hasPlus` is read off the
|
||||
// account ROW rather than a token claim, because it's set after the browser's token was
|
||||
// issued — a freshly-claimed player's token says nothing about it.
|
||||
it('reports where an account stands on benefits', async () => {
|
||||
const token = await tokenFor(4003)
|
||||
|
||||
// An account with no row at all reads as "nothing claimed" rather than 404ing: every
|
||||
// account has a benefits status, whether or not it has been written to yet.
|
||||
let res = await SELF.fetch('https://example.com/api/benefits/status', {
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({ hasPlus: false, linked: false })
|
||||
|
||||
await updateAccount(env.DB, 4003, { hasPlus: true })
|
||||
await linkPlatformIdentity(env.DB, 4003, PlatformType.Discord, '99001')
|
||||
res = await SELF.fetch('https://example.com/api/benefits/status', {
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
})
|
||||
// `linked` says THAT a Discord identity is attached, never which one — the id is of no
|
||||
// use to the page, and an account's linked identities have no business on the wire.
|
||||
expect(await res.json()).toEqual({ hasPlus: true, linked: true })
|
||||
})
|
||||
|
||||
// The once-only guard the claim is built on. Without it, one Discord member holding the
|
||||
// role could walk it around every RecFlare account they own; with it, the second claim is
|
||||
// refused and the first account keeps the benefit. Exercised at the two lookups the route
|
||||
// asks, since the route's own path to them runs through discord.com.
|
||||
it('tells a repeat claim from a second account claiming the same discord identity', async () => {
|
||||
await updateAccount(env.DB, 4004, { hasPlus: true })
|
||||
await linkPlatformIdentity(env.DB, 4004, PlatformType.Discord, '99002')
|
||||
|
||||
// The identity is taken, so a DIFFERENT account claiming it is the 409 case…
|
||||
await expect(
|
||||
countAccountsForPlatformIdentity(env.DB, PlatformType.Discord, '99002')
|
||||
).resolves.toBe(1)
|
||||
await expect(isPlatformIdentityLinked(env.DB, 4005, PlatformType.Discord, '99002')).resolves.toBe(
|
||||
false
|
||||
)
|
||||
|
||||
// …while the account that already holds it re-claims idempotently, which is what makes
|
||||
// the page safe to reload and a lapsed-then-restored role re-claimable.
|
||||
await expect(isPlatformIdentityLinked(env.DB, 4004, PlatformType.Discord, '99002')).resolves.toBe(
|
||||
true
|
||||
)
|
||||
|
||||
// A Discord member who has claimed nowhere yet.
|
||||
await expect(
|
||||
countAccountsForPlatformIdentity(env.DB, PlatformType.Discord, '99003')
|
||||
).resolves.toBe(0)
|
||||
})
|
||||
|
||||
// A Discord link must never become a way INTO an account. The login picker is public and
|
||||
// unauthenticated, so listing one would both offer the client an account it can't redeem
|
||||
// (the grant refuses platform 101) and tell anyone which RecFlare account a Discord user
|
||||
// owns — a snowflake is readable by anyone sharing a server with them. `auth` owns that
|
||||
// gate; this pins that the platform www writes to is one the gate actually excludes.
|
||||
it('stores the discord identity on a platform the login picker will not list', () => {
|
||||
expect(CACHED_LOGIN_PLATFORMS).not.toContain(PlatformType.Discord)
|
||||
})
|
||||
|
||||
// The privacy policy is what the Meta Horizon Store's VRC.Privacy.1–4 checks are run
|
||||
// against, and a reviewer only sees the rendered page — so the four things they look
|
||||
// for are pinned here. If a section is renamed, re-read the VRC before loosening the
|
||||
|
||||
+206
-1
@@ -1,10 +1,35 @@
|
||||
import { Hono } from 'hono'
|
||||
import { useWorkersLogger } from 'workers-tagged-logger'
|
||||
|
||||
import { getAccount, updateAccount } from '@repo/domain/src/accounts-db'
|
||||
import { PlatformType } from '@repo/domain/src/enums'
|
||||
import { countOnlinePlayers } from '@repo/domain/src/presence-db'
|
||||
import { logger, withDefaultCors, withOnError } from '@repo/hono-helpers'
|
||||
import { validateAndGetAccountId } from '@repo/jwt'
|
||||
|
||||
// The `platform_account` link table, owned (and migrated) by the `auth` worker. A claimed
|
||||
// Discord identity is stored there as a PlatformType.Discord link — it is an account ↔
|
||||
// external identity exactly like the Steam and Meta ones, and unlike a field on the
|
||||
// account blob it can answer "is this Discord user already on another account" from an
|
||||
// index. Nobody logs in with it: `auth` refuses a cached_login for any platform it can't
|
||||
// verify, and its picker lists only the platforms that can be (CACHED_LOGIN_PLATFORMS).
|
||||
import {
|
||||
countAccountsForPlatformIdentity,
|
||||
getLinksForAccount,
|
||||
isPlatformIdentityLinked,
|
||||
linkPlatformIdentity,
|
||||
} from '../../auth/src/platform-db'
|
||||
import { authUnreachable } from './auth-messages'
|
||||
import {
|
||||
AUTHORIZE_URL,
|
||||
discordConfig,
|
||||
exchangeCode,
|
||||
fetchGuildMembership,
|
||||
qualifies,
|
||||
redirectUri,
|
||||
revokeToken,
|
||||
SCOPES,
|
||||
} from './discord'
|
||||
import { docsPage, fetchSpec } from './docs'
|
||||
import { privacyPage } from './privacy'
|
||||
import { turnstileKeys, verifyTurnstile } from './turnstile'
|
||||
@@ -21,6 +46,7 @@ import {
|
||||
storageBase,
|
||||
} from './upstream'
|
||||
|
||||
import type { Context } from 'hono'
|
||||
import type { App } from './context'
|
||||
|
||||
/**
|
||||
@@ -37,8 +63,49 @@ import type { App } from './context'
|
||||
* there's no client contract being duplicated.
|
||||
* - `/api/config`, which tells the SPA the Turnstile site key and where the other
|
||||
* workers live, so one client build works for any operator's domain.
|
||||
* - `/api/benefits/*`, the Discord-verified benefits claim, for the same reason as
|
||||
* signup: the OAuth2 client secret that turns Discord's authorization code into an
|
||||
* access token can't ship to a browser. It is also the only route here that writes to
|
||||
* the database, and so the only one that verifies a token itself (see the section).
|
||||
*/
|
||||
|
||||
/**
|
||||
* The account behind a request's bearer token, or null.
|
||||
*
|
||||
* www verifies a token itself for exactly one feature. Everywhere else the browser
|
||||
* carries its token to the worker that owns the data (`accounts`, `rooms`, …) and that
|
||||
* worker does the checking; but the benefits claim WRITES `hasPlus` onto an account row,
|
||||
* and "which account" is the whole question — asking the SPA would let anyone grant Plus
|
||||
* to any id. Same key, same `@repo/jwt` validation (signature, exp) every other worker
|
||||
* runs.
|
||||
*/
|
||||
const claimant = async (c: Context<App>): Promise<number | null> =>
|
||||
validateAndGetAccountId(c.req.raw, await c.env.JWT_SECRET.get())
|
||||
|
||||
/**
|
||||
* The Discord consent URL the SPA sends the player to, fully assembled here rather than
|
||||
* in the browser — everything in it (the scopes the claim needs, and the redirect URI,
|
||||
* which must match the token exchange byte for byte) is this worker's business, and a
|
||||
* page that built its own could drift from what `/api/benefits/claim` will accept.
|
||||
*
|
||||
* It carries no `state`. That is the SPA's to add and to check on the way back: it's a
|
||||
* per-attempt CSRF nonce, so it has to be minted by the thing that will later verify it
|
||||
* (see App.tsx). Everything else about the request is fixed by the server.
|
||||
*/
|
||||
function authorizeUrl(request: Request, clientId: string): string {
|
||||
const url = new URL(AUTHORIZE_URL)
|
||||
url.search = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
response_type: 'code',
|
||||
scope: SCOPES,
|
||||
redirect_uri: redirectUri(request),
|
||||
// Skip Discord's "you've already authorized this app, continue?" interstitial on a
|
||||
// repeat claim; the player has already pressed a button that says what this does.
|
||||
prompt: 'none',
|
||||
}).toString()
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
const app = new Hono<App>()
|
||||
.use(
|
||||
'*',
|
||||
@@ -60,10 +127,16 @@ const app = new Hono<App>()
|
||||
// one build works for any operator. The site key is public (it ships in the widget
|
||||
// markup either way); the secret never leaves the worker.
|
||||
.get('/api/config', async (c) => {
|
||||
const keys = await turnstileKeys(c.env)
|
||||
const [keys, discord] = await Promise.all([turnstileKeys(c.env), discordConfig(c.env)])
|
||||
return c.json({
|
||||
signupEnabled: keys !== null,
|
||||
turnstileSiteKey: keys?.siteKey ?? null,
|
||||
// The benefits claim, on the same terms: open only when it's fully configured, and
|
||||
// the SPA is handed a ready-made consent URL rather than the parts to build one.
|
||||
// Nothing secret is served — the client id inside it is public — and the guild/role
|
||||
// ids never leave the worker, since it's the worker that asks Discord the question.
|
||||
benefitsEnabled: discord !== null,
|
||||
discordAuthorizeUrl: discord ? authorizeUrl(c.req.raw, discord.clientId) : null,
|
||||
hosts: {
|
||||
auth: authBase(c.env),
|
||||
accounts: accountsBase(c.env),
|
||||
@@ -169,6 +242,138 @@ const app = new Hono<App>()
|
||||
return c.json(token)
|
||||
})
|
||||
|
||||
// ---- Discord benefits claim ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Where the player's Discord already stands with this account — what the claim page
|
||||
* renders before anyone presses anything, so a player who has already claimed sees
|
||||
* that rather than being walked through the flow again to find out.
|
||||
*
|
||||
* `hasPlus` is read from the account row rather than from a token claim: it is set
|
||||
* here, after the token in the browser was issued, so a freshly-claimed player's token
|
||||
* says nothing about it until they sign in again.
|
||||
*/
|
||||
.get('/api/benefits/status', async (c) => {
|
||||
const accountId = await claimant(c)
|
||||
if (accountId === null) return c.body(null, 401)
|
||||
const [account, links] = await Promise.all([
|
||||
getAccount(c.env.DB, accountId),
|
||||
getLinksForAccount(c.env.DB, accountId),
|
||||
])
|
||||
return c.json({
|
||||
hasPlus: account?.hasPlus ?? false,
|
||||
// Whether this account is already tied to a Discord identity — not WHICH one. The
|
||||
// player knows their own Discord; the id is of no use to the page and every reason
|
||||
// to keep an account's linked identities off the wire.
|
||||
linked: links.some((link) => link.platform === PlatformType.Discord),
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Redeem a Discord authorization code and, if the player holds the configured role in
|
||||
* the configured guild, give the account Rec Room Plus.
|
||||
*
|
||||
* The browser sends ONLY the code. It never sees an access token: the exchange happens
|
||||
* here with the client secret, the roles are read with the resulting token, and the
|
||||
* token is handed straight back to Discord (see discord.ts). The redirect URI is
|
||||
* derived from this request's own origin rather than accepted from the body, so the
|
||||
* secret can't be used to redeem codes issued for somebody else's page.
|
||||
*
|
||||
* The claim is once-only PER DISCORD USER, not per account: the Discord id is stored
|
||||
* beside the flag, and a code from a Discord member who has already claimed elsewhere
|
||||
* is refused. Otherwise one person with the role could walk it around every account
|
||||
* they own. Re-claiming on the same account is allowed and simply re-affirms the flag,
|
||||
* which is what makes the page safe to reload and a lapsed-then-restored role
|
||||
* re-claimable.
|
||||
*
|
||||
* Nothing here ever REVOKES Plus: losing the Discord role later leaves the flag set.
|
||||
* That's deliberate for now — a sweep would need a bot token to enumerate the guild,
|
||||
* which this design specifically avoids — but it does mean the flag records "held the
|
||||
* role once", not "holds it today".
|
||||
*/
|
||||
.post('/api/benefits/claim', async (c) => {
|
||||
const config = await discordConfig(c.env)
|
||||
if (!config) return c.json({ error: 'Benefit claims are currently disabled.' }, 403)
|
||||
|
||||
const accountId = await claimant(c)
|
||||
if (accountId === null) {
|
||||
return c.json({ error: 'Please sign in before claiming your benefits.' }, 401)
|
||||
}
|
||||
|
||||
type ClaimBody = { code?: string }
|
||||
const { code } = await c.req.json<ClaimBody>().catch(() => ({}) as ClaimBody)
|
||||
if (!code) return c.json({ error: 'No Discord authorization code was provided.' }, 400)
|
||||
|
||||
const accessToken = await exchangeCode(config, code, redirectUri(c.req.raw))
|
||||
// A code lives about a minute and is single-use, so this is far and away the most
|
||||
// likely failure a real player hits — hence a sentence about starting over rather
|
||||
// than a relayed OAuth code, which would tell them nothing.
|
||||
if (accessToken === null) {
|
||||
return c.json(
|
||||
{ error: 'That Discord sign-in could not be completed. Please try again.' },
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
const membership = await fetchGuildMembership(accessToken, config.guildId)
|
||||
// The token has told us everything it can; hand it back before answering, whatever
|
||||
// the answer turns out to be. Awaited rather than fired into the void so a Worker
|
||||
// that finishes the response can't cancel it.
|
||||
await revokeToken(config, accessToken)
|
||||
|
||||
if (membership === null) {
|
||||
return c.json({ error: 'You are not a member of our Discord server.' }, 403)
|
||||
}
|
||||
// Any ONE of the configured roles qualifies — see `qualifies`. The message stays
|
||||
// singular-ish and names no role: which roles qualify is the operator's business to
|
||||
// advertise in their own server, and listing them here would leak the guild's role
|
||||
// layout to anyone who pressed the button.
|
||||
if (!qualifies(membership.roles, config.roleIds)) {
|
||||
return c.json({ error: 'Your Discord account does not have a qualifying role.' }, 403)
|
||||
}
|
||||
|
||||
// The once-only guard, asked of the link table: is this Discord identity already on an
|
||||
// account, and is that account someone else's? Re-claiming on the caller's OWN account
|
||||
// is the idempotent case and must fall through — it's how a player whose role lapsed
|
||||
// and came back re-affirms Plus, and it's what makes the page safe to reload.
|
||||
//
|
||||
// `countAccountsForPlatformIdentity` counts EVERY link for the identity, unfiltered by
|
||||
// platform, which is why the claim can use the same helper `auth`'s per-identity
|
||||
// signup cap does.
|
||||
const alreadyMine = await isPlatformIdentityLinked(
|
||||
c.env.DB,
|
||||
accountId,
|
||||
PlatformType.Discord,
|
||||
membership.userId
|
||||
)
|
||||
if (!alreadyMine) {
|
||||
const claimedElsewhere = await countAccountsForPlatformIdentity(
|
||||
c.env.DB,
|
||||
PlatformType.Discord,
|
||||
membership.userId
|
||||
)
|
||||
if (claimedElsewhere > 0) {
|
||||
logger.info('a discord account tried to claim benefits on a second account', {
|
||||
accountId,
|
||||
})
|
||||
return c.json(
|
||||
{ error: 'That Discord account has already claimed benefits on another account.' },
|
||||
409
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Both writes are idempotent: the link is INSERT OR IGNORE (so `linkedAt` keeps the
|
||||
// FIRST claim's time), and the flag is already true on a re-claim.
|
||||
await linkPlatformIdentity(c.env.DB, accountId, PlatformType.Discord, membership.userId)
|
||||
await updateAccount(c.env.DB, accountId, { hasPlus: true })
|
||||
logger.info('granted plus from a discord benefits claim', { accountId })
|
||||
|
||||
// The username is echoed for the confirmation line only — it is never stored, and a
|
||||
// Discord member who has since renamed themselves is not a problem to solve here.
|
||||
return c.json({ hasPlus: true, discordUsername: membership.username })
|
||||
})
|
||||
|
||||
// ---- Privacy policy -----------------------------------------------------
|
||||
// Server-rendered rather than a SPA route so the page has real text without
|
||||
// JavaScript: the Meta Horizon Store re-fetches this URL to check the policy is
|
||||
|
||||
+51
-1
@@ -66,6 +66,37 @@
|
||||
"binding": "TURNSTILE_SECRET_KEY",
|
||||
"store_id": "local",
|
||||
"secret_name": "TURNSTILE_SECRET_KEY"
|
||||
},
|
||||
// The shared HS256 signing key, bound here for the ONE www route that acts on a
|
||||
// specific account: the benefits claim writes `hasPlus` onto the caller's row, so it
|
||||
// has to verify which account is calling rather than trust the SPA.
|
||||
{
|
||||
"binding": "JWT_SECRET",
|
||||
"store_id": "local",
|
||||
"secret_name": "JWT_SECRET"
|
||||
},
|
||||
// The Discord OAuth2 application behind the benefits claim. Same store, same
|
||||
// public-key-beside-its-secret arrangement as the Turnstile pair: the client id
|
||||
// ships to the browser to build the authorize URL, the secret never leaves the
|
||||
// worker (see src/discord.ts).
|
||||
//
|
||||
// wrangler secrets-store secret create <store-id> --name DISCORD_CLIENT_ID \
|
||||
// --scopes workers --remote
|
||||
// wrangler secrets-store secret create <store-id> --name DISCORD_CLIENT_SECRET \
|
||||
// --scopes workers --remote
|
||||
//
|
||||
// These two PLUS the DISCORD_GUILD_ID / DISCORD_BENEFITS_ROLE_IDS vars below are what
|
||||
// OPENS the claim; with any of the four missing it stays closed, so an operator who
|
||||
// skips this gets no claim rather than one that grants Plus without checking.
|
||||
{
|
||||
"binding": "DISCORD_CLIENT_ID",
|
||||
"store_id": "local",
|
||||
"secret_name": "DISCORD_CLIENT_ID"
|
||||
},
|
||||
{
|
||||
"binding": "DISCORD_CLIENT_SECRET",
|
||||
"store_id": "local",
|
||||
"secret_name": "DISCORD_CLIENT_SECRET"
|
||||
}
|
||||
],
|
||||
// The `auth` worker, reached directly instead of over its public hostname. This is
|
||||
@@ -97,6 +128,25 @@
|
||||
// (see run-wrangler-deploy). www serves these to the SPA via `/api/config`, which
|
||||
// is how one client build works for any operator. For local dev, point it at a
|
||||
// deployed domain so the page has real workers to call.
|
||||
"DOMAIN": "rec.example.com"
|
||||
"DOMAIN": "rec.example.com",
|
||||
// The Discord server the benefits claim checks membership of, and the roles in it
|
||||
// that grant Rec Room Plus. Every value here is a Discord SNOWFLAKE — all digits, no
|
||||
// letters — copied off a client with Developer Mode on (right-click the server or the
|
||||
// role → Copy ID). They are ids, not names: "Supporter" is what the role is called,
|
||||
// 1077000000000000002 is what goes here. Not credentials, so they live in this file
|
||||
// rather than in the Secrets Store; quoted as STRINGS because a snowflake exceeds
|
||||
// 2^53 and would lose precision as a JSON number.
|
||||
//
|
||||
// ROLE_IDS is a LIST, separated by commas and/or whitespace. Any ONE of them
|
||||
// qualifies, so several tiers can share the benefit:
|
||||
//
|
||||
// "DISCORD_BENEFITS_ROLE_IDS": "1077000000000000001,1077000000000000002"
|
||||
//
|
||||
// Empty by default: an operator who hasn't set up a Discord app has no server to
|
||||
// point at, and an empty value (or one that parses to no ids) closes the claim — see
|
||||
// src/discord.ts `discordConfig` — instead of leaving a form that grants Plus to
|
||||
// anyone who signs in with Discord.
|
||||
"DISCORD_GUILD_ID": "",
|
||||
"DISCORD_BENEFITS_ROLE_IDS": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,30 @@ export interface Account {
|
||||
* `runx admin grant-moderator`. Absent/false means no role.
|
||||
*/
|
||||
isModerator?: boolean
|
||||
/**
|
||||
* Whether this account has Rec Room Plus — the paid tier the client's API calls a
|
||||
* `CampusCard`. Nothing SELLS one here. Absent/false means no Plus.
|
||||
*
|
||||
* This flag ALONE is what confers it, and it stands on its own: two things set it, and
|
||||
* neither is a precondition of the other.
|
||||
*
|
||||
* - the website's benefits claim (`www` `POST /api/benefits/claim`), where a player
|
||||
* proves a qualifying role in the community Discord. That path also links their
|
||||
* Discord identity into `platform_account` as a `PlatformType.Discord` row — but the
|
||||
* link exists to keep the CLAIM once-only per Discord user, not to justify the flag.
|
||||
* - an operator, via `runx admin grant-plus`, with no Discord anywhere in sight.
|
||||
*
|
||||
* So never read a Discord link as a precondition for Plus, and never revoke one because
|
||||
* the other is missing: a manually granted account has `hasPlus` and no link at all, and
|
||||
* that is a normal, supported state.
|
||||
*
|
||||
* Nothing reads this per request. `auth` stamps it into every token it mints as the
|
||||
* `rn.plus` claim, and `econ` decides the CampusCard and the subscriber discount from
|
||||
* that claim alone — so setting it takes effect on the account's NEXT login, not
|
||||
* immediately. Tokens last a day and the client never refreshes them, so that lag is
|
||||
* real: the website's claim page warns about it, and so does `grant-plus`.
|
||||
*/
|
||||
hasPlus?: boolean
|
||||
}
|
||||
|
||||
interface AccountRow {
|
||||
|
||||
@@ -5,6 +5,39 @@
|
||||
* the tsconfig sets `isolatedModules` (which disallows `const enum` across files).
|
||||
*/
|
||||
|
||||
/**
|
||||
* PlatformType, the client's platform enum. Declaration order is wire order. The
|
||||
* `platform` form field is posted as the integer; a token's `platform` claim carries it
|
||||
* too. `auth` re-exports this as the source for its OpenAPI schema and description.
|
||||
*
|
||||
* A plain `as const` object rather than an `enum` like its neighbours, and deliberately
|
||||
* so: `auth` builds `PlatformTypeSchema`'s description by walking `Object.entries`, and a
|
||||
* numeric TS enum also emits a REVERSE mapping (`{ '0': 'Steam', Steam: 0, … }`), which
|
||||
* would double every member in the generated spec.
|
||||
*
|
||||
* Everything from `Steam` to `Pico` is a real Rec Room client platform, numbered by the
|
||||
* client. `Discord` is OURS — it is not a platform anyone signs in from, and the client
|
||||
* never sends it. It exists so a verified Discord identity can be stored as an account
|
||||
* link like any other external identity (see `auth`'s platform-db and the website's
|
||||
* benefits claim); it sits at 101, well clear of the client's range, so a future client
|
||||
* platform can be added without colliding with it.
|
||||
*/
|
||||
export const PlatformType = {
|
||||
All: -1,
|
||||
Steam: 0,
|
||||
Oculus: 1,
|
||||
PlayStation: 2,
|
||||
Xbox: 3,
|
||||
RecNet: 4,
|
||||
IOS: 5,
|
||||
GooglePlay: 6,
|
||||
Standalone: 7,
|
||||
Pico: 8,
|
||||
Discord: 101,
|
||||
} as const
|
||||
|
||||
export type PlatformType = (typeof PlatformType)[keyof typeof PlatformType]
|
||||
|
||||
/** The kind of a room instance (live session), matching the client's `RoomInstanceType`. */
|
||||
export enum RoomInstanceType {
|
||||
Public = 0,
|
||||
|
||||
@@ -15,6 +15,7 @@ export * from './room-instance-db'
|
||||
export * from './room-comments-db'
|
||||
export * from './room-invites-db'
|
||||
export * from './presence-db'
|
||||
export * from './stats-db'
|
||||
export * from './gifts-db'
|
||||
export * from './inventory-invention-db'
|
||||
export * from './lists-db'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Owned inventions on the shared `recflare` D1 database — the inventions a player has
|
||||
* bought. One row per (account, invention), written at purchase time by the `econ`
|
||||
* worker's `GET /api/storefronts/v2/buyInvention`.
|
||||
* worker's buyInvention — the `v2` GET and the `v3` POST, which share one settlement.
|
||||
*
|
||||
* Only the invention id is stored: the invention record itself lives in the `invention`
|
||||
* table, whose schema the `api` worker owns (apps/api/migrations/0002_invention.sql) on
|
||||
|
||||
@@ -12,9 +12,21 @@
|
||||
const ITERATIONS = 100_000
|
||||
|
||||
const b64 = (bytes: Uint8Array): string => btoa(String.fromCharCode(...bytes))
|
||||
const fromB64 = (s: string): Uint8Array => Uint8Array.from(atob(s), (ch) => ch.charCodeAt(0))
|
||||
const fromB64 = (s: string): Uint8Array<ArrayBuffer> =>
|
||||
Uint8Array.from(atob(s), (ch) => ch.charCodeAt(0))
|
||||
|
||||
async function deriveBits(password: string, salt: Uint8Array): Promise<Uint8Array> {
|
||||
/**
|
||||
* The salt is `Uint8Array<ArrayBuffer>` rather than a bare `Uint8Array` because the latter
|
||||
* is `Uint8Array<ArrayBufferLike>`, which admits a `SharedArrayBuffer` — and the DOM lib's
|
||||
* `BufferSource` does not. Both callers already produce a plain-ArrayBuffer view
|
||||
* (`getRandomValues` and `fromB64`), so this only writes down what was always true; without
|
||||
* it, any worker whose tsconfig includes the DOM lib (`www`, for its React client) fails to
|
||||
* compile on the `deriveBits` call below.
|
||||
*/
|
||||
async function deriveBits(
|
||||
password: string,
|
||||
salt: Uint8Array<ArrayBuffer>
|
||||
): Promise<Uint8Array<ArrayBuffer>> {
|
||||
const keyMaterial = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
new TextEncoder().encode(password),
|
||||
|
||||
@@ -33,21 +33,25 @@ export const PRESENCE_TTL_SECONDS = 900
|
||||
export const GAME_VERSION = '20230414'
|
||||
|
||||
/**
|
||||
* Client builds `/api/versioncheck/v4` answers "current" for. `GAME_VERSION` is the one
|
||||
* the rest of the stack targets and reports for itself; the others are later clients
|
||||
* that talk close enough to the same protocol to get past the update prompt.
|
||||
* Client builds `/api/versioncheck/v4` answers "current" for, in the support tiers the
|
||||
* README publishes. They talk close enough to the same protocol to get past the update
|
||||
* prompt; how much beyond that is tested differs by tier:
|
||||
*
|
||||
* DEBUGGING ONLY beyond `GAME_VERSION`: this is not a supported-version list. Nothing
|
||||
* else in the stack targets those builds, so a client waved through here can still hit
|
||||
* protocol differences the version check would otherwise have caught. Trim it back to
|
||||
* `GAME_VERSION` alone before anyone but us is playing.
|
||||
* - `GAME_VERSION` (`20230414`, manifest `7859140924515540835`) is the DEFAULT — the
|
||||
* build the rest of the stack targets and the one it reports for itself.
|
||||
* - `20250718.01` (manifest `1151455856673601091`, reached through the `patch-2025`
|
||||
* patch) is BETA — supported, and the build the "newer than `20230414`" gates in
|
||||
* `econ`/`api`/`rooms` are written and tested against.
|
||||
* - The rest are ALPHA: waved past the version check, but nothing else in the stack
|
||||
* targets them, so they can still hit protocol differences this check would otherwise
|
||||
* have caught. Expect breakage rather than treating a bug there as a regression.
|
||||
*/
|
||||
export const SUPPORTED_GAME_VERSIONS: string[] = [
|
||||
GAME_VERSION,
|
||||
'20230616',
|
||||
'20231207',
|
||||
'20250424.01',
|
||||
'20250718.01',
|
||||
GAME_VERSION, // default
|
||||
'20250718.01', // beta
|
||||
'20230616', // alpha
|
||||
'20231207', // alpha
|
||||
'20250424.01', // alpha
|
||||
]
|
||||
|
||||
/** Whether a client-supplied build (the version check's `?v=`) is one we serve. */
|
||||
|
||||
@@ -85,6 +85,38 @@ export async function getRoomInvite(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The newest live invite from `fromPlayerId` to `toPlayerId`, or null when none stands.
|
||||
*
|
||||
* This is the by-PLAYER-pair lookup behind `POST /matchmake/v2/player/:playerId`, where
|
||||
* the caller redeems "an invite from that player" without holding a `RoomInviteId` (the
|
||||
* newer client's invite frame doesn't always carry a usable one). Newest by id — ids are
|
||||
* AUTOINCREMENT, so the largest is the most recently sent — and, like
|
||||
* {@link getRoomInvite}, a miss covers both "never invited" and "already swept".
|
||||
*/
|
||||
export async function getLatestRoomInviteBetween(
|
||||
db: D1Database,
|
||||
fromPlayerId: number,
|
||||
toPlayerId: number
|
||||
): Promise<RoomInvite | null> {
|
||||
const row = await db
|
||||
.prepare(
|
||||
`SELECT ${SELECT_COLUMNS} FROM room_invite
|
||||
WHERE from_player_id = ?1 AND to_player_id = ?2
|
||||
ORDER BY room_invite_id DESC LIMIT 1`
|
||||
)
|
||||
.bind(fromPlayerId, toPlayerId)
|
||||
.first<RoomInviteRow>()
|
||||
|
||||
if (!row) return null
|
||||
return {
|
||||
RoomInviteId: row.room_invite_id,
|
||||
FromPlayerId: row.from_player_id,
|
||||
ToPlayerId: row.to_player_id,
|
||||
RoomId: row.room_id,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an invite from `fromPlayerId` to `toPlayerId` for a room, returning it as the
|
||||
* client reads it back. `roomId` is null when the caller's room instance didn't resolve.
|
||||
@@ -115,3 +147,21 @@ export async function createRoomInvite(
|
||||
RoomId: row.room_id,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete one invite by its id, answering whether a row was there to delete.
|
||||
*
|
||||
* An invite is single-use: `POST /matchmake/v2/player/:playerId` redeems the newest row
|
||||
* from the target and drops it here once the caller is actually in the instance, so a
|
||||
* standing invite doesn't stay a permanent key into whatever room that player is in
|
||||
* later. Deleting rather than flagging matches the expiry sweep, which is why every
|
||||
* lookup reads a miss as "no longer good" without a status column.
|
||||
*/
|
||||
export async function deleteRoomInvite(db: D1Database, roomInviteId: number): Promise<boolean> {
|
||||
const row = await db
|
||||
.prepare(`DELETE FROM room_invite WHERE room_invite_id = ?1 RETURNING room_invite_id`)
|
||||
.bind(roomInviteId)
|
||||
.first<{ room_invite_id: number }>()
|
||||
|
||||
return row !== null
|
||||
}
|
||||
|
||||
+244
-48
@@ -103,6 +103,40 @@ export const ROOM_SCHEMA_DDL: string[] = [
|
||||
PRIMARY KEY (room_id, banned_player_id)
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_room_ban_player ON room_ban (banned_player_id)`,
|
||||
// Per-room leaderboard definitions (migrations/0016_room_leaderboard.sql). One row per
|
||||
// (room, leaderboard): `leaderboard_id` is the client's slot number — small ordinals
|
||||
// (1, 2, 3…), unique only within the room — so the pair is the key, and re-posting a
|
||||
// slot reconfigures it in place rather than appending.
|
||||
//
|
||||
// Deliberately NOT in the room's `data` blob, same reasoning as `room_ban`: the blob is
|
||||
// served verbatim as the room and the client doesn't read leaderboards off it.
|
||||
`CREATE TABLE IF NOT EXISTS room_leaderboard (
|
||||
room_id INTEGER NOT NULL,
|
||||
leaderboard_id INTEGER NOT NULL,
|
||||
leaderboard_title TEXT NOT NULL,
|
||||
stat_format INTEGER NOT NULL DEFAULT 0,
|
||||
sort_ascending INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (room_id, leaderboard_id)
|
||||
)`,
|
||||
// Per-room role grants and invitations (migrations/0017_room_role.sql), one row per
|
||||
// (room, account) — the client's `Roles` entry shape ({@link RoomRole}): `role` is the
|
||||
// member's CURRENT role tier, `invited_role` the tier they've been offered but not yet
|
||||
// accepted (so it's usually the higher of the two; 0 when no offer is pending), and
|
||||
// `last_changed_by` who last touched the row (NULL for the seeded creator entries,
|
||||
// matching the blob's `LastChangedByAccountId: null`).
|
||||
//
|
||||
// The table is AUTHORITATIVE, the same arrangement as `room_tag`: `serializeRoom`
|
||||
// strips `Roles` from the blob and the reads re-attach it (see {@link attachRoles}),
|
||||
// so there is exactly one place a role is stored — and `getContributedRooms` matches
|
||||
// on an indexed table instead of a `json_each` over every blob.
|
||||
`CREATE TABLE IF NOT EXISTS room_role (
|
||||
room_id INTEGER NOT NULL,
|
||||
account_id INTEGER NOT NULL,
|
||||
role INTEGER NOT NULL DEFAULT 0,
|
||||
last_changed_by INTEGER,
|
||||
invited_role INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (room_id, account_id)
|
||||
)`,
|
||||
]
|
||||
|
||||
/**
|
||||
@@ -196,6 +230,22 @@ export function canManageRoom(room: Room, accountId: number): boolean {
|
||||
return roles.some((r) => r.AccountId === accountId && MANAGE_ROLES.has(r.Role))
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an account may MODERATE a room — its creator, or the holder of a role at
|
||||
* Moderator (20) or above. The wider gate that {@link canManageRoom} is the narrow one
|
||||
* of: a moderator polices who is in the room right now (kicking someone out of an
|
||||
* instance) without being trusted to change the room itself, while everyone who can
|
||||
* manage a room can obviously also police it, so CoOwner and Creator pass here too.
|
||||
*
|
||||
* Host (10) is deliberately below the line: it is the "runs this session" tier, which the
|
||||
* client hands out freely, and a kick is a moderation power rather than a hosting one.
|
||||
*/
|
||||
export function canModerateRoom(room: Room, accountId: number): boolean {
|
||||
if (room.CreatorAccountId === accountId) return true
|
||||
const roles = Array.isArray(room.Roles) ? (room.Roles as RoomRole[]) : []
|
||||
return roles.some((r) => r.AccountId === accountId && r.Role >= Role.Moderator)
|
||||
}
|
||||
|
||||
/** A player banned from a room (a `room_ban` row). */
|
||||
export interface RoomBan {
|
||||
RoomId: number
|
||||
@@ -286,6 +336,75 @@ export async function isPlayerBannedFromRoom(
|
||||
return row !== null
|
||||
}
|
||||
|
||||
/** A room's leaderboard definition — one configured slot (`leaderboard_id` is per-room). */
|
||||
export interface RoomLeaderboard {
|
||||
RoomId: number
|
||||
LeaderboardId: number
|
||||
LeaderboardTitle: string
|
||||
StatFormat: number
|
||||
SortAscending: boolean
|
||||
}
|
||||
|
||||
interface RoomLeaderboardRow {
|
||||
room_id: number
|
||||
leaderboard_id: number
|
||||
leaderboard_title: string
|
||||
stat_format: number
|
||||
sort_ascending: number
|
||||
}
|
||||
|
||||
const toRoomLeaderboard = (row: RoomLeaderboardRow): RoomLeaderboard => ({
|
||||
RoomId: row.room_id,
|
||||
LeaderboardId: row.leaderboard_id,
|
||||
LeaderboardTitle: row.leaderboard_title,
|
||||
StatFormat: row.stat_format,
|
||||
SortAscending: row.sort_ascending === 1,
|
||||
})
|
||||
|
||||
/**
|
||||
* Create or reconfigure one of a room's leaderboard slots, returning the stored
|
||||
* definition. One row per (room, leaderboard): re-posting a slot rewrites its title,
|
||||
* format and direction rather than appending a second row, so the call is idempotent.
|
||||
*/
|
||||
export async function setRoomLeaderboard(
|
||||
db: D1Database,
|
||||
roomId: number,
|
||||
leaderboardId: number,
|
||||
leaderboardTitle: string,
|
||||
statFormat: number,
|
||||
sortAscending: boolean
|
||||
): Promise<RoomLeaderboard> {
|
||||
const row = await db
|
||||
.prepare(
|
||||
`INSERT INTO room_leaderboard (room_id, leaderboard_id, leaderboard_title, stat_format, sort_ascending)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
ON CONFLICT(room_id, leaderboard_id) DO UPDATE SET
|
||||
leaderboard_title = ?3, stat_format = ?4, sort_ascending = ?5
|
||||
RETURNING *`
|
||||
)
|
||||
.bind(roomId, leaderboardId, leaderboardTitle, statFormat, sortAscending ? 1 : 0)
|
||||
.first<RoomLeaderboardRow>()
|
||||
// RETURNING always yields the upserted row.
|
||||
return toRoomLeaderboard(row!)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove one of a room's leaderboard slots, returning the definition that was removed —
|
||||
* or null when the slot wasn't configured, which lets the caller tell a real delete
|
||||
* from a no-op.
|
||||
*/
|
||||
export async function deleteRoomLeaderboard(
|
||||
db: D1Database,
|
||||
roomId: number,
|
||||
leaderboardId: number
|
||||
): Promise<RoomLeaderboard | null> {
|
||||
const row = await db
|
||||
.prepare('DELETE FROM room_leaderboard WHERE room_id = ?1 AND leaderboard_id = ?2 RETURNING *')
|
||||
.bind(roomId, leaderboardId)
|
||||
.first<RoomLeaderboardRow>()
|
||||
return row ? toRoomLeaderboard(row) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing room into a new one owned by `accountId`. Copies the source
|
||||
* room's content (scene/subrooms/settings), assigning a fresh RoomId, the given
|
||||
@@ -312,7 +431,8 @@ export async function cloneRoom(
|
||||
|
||||
// Ownership is reset to the cloner — the source room's Roles (its creator and
|
||||
// any co-owners, e.g. the seeded base-room roles for accounts 1/2) must NOT
|
||||
// carry over, or the clone would still list the template's owner as owner.
|
||||
// carry over, or the clone would still list the template's owner as owner. The
|
||||
// roles live in `room_role` (inserted below); this array is the response copy.
|
||||
const roles: RoomRole[] = [
|
||||
{ AccountId: accountId, Role: Role.Creator, LastChangedByAccountId: null, InvitedRole: 0 },
|
||||
]
|
||||
@@ -340,9 +460,11 @@ export async function cloneRoom(
|
||||
CreatedAt: new Date().toISOString(),
|
||||
}
|
||||
|
||||
// serializeRoom drops the hydrated SubRooms from the blob; the clone's subrooms are
|
||||
// inserted into the subroom table below with fresh globally-unique ids.
|
||||
// serializeRoom drops the hydrated SubRooms from the blob (and Roles — those go to
|
||||
// their own table); the clone's subrooms are inserted into the subroom table below
|
||||
// with fresh globally-unique ids.
|
||||
await db.prepare('INSERT INTO room (data) VALUES (?1)').bind(serializeRoom(cloned)).run()
|
||||
await insertCreatorRole(db, newRoomId, accountId)
|
||||
const sourceSubRooms = Array.isArray(source.SubRooms) ? (source.SubRooms as SubRoom[]) : []
|
||||
const clonedSubRooms: SubRoom[] = []
|
||||
for (const sub of sourceSubRooms) {
|
||||
@@ -367,23 +489,10 @@ export async function setRoomDescription(
|
||||
.run()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a room's Name in place (the caller checks ownership + name uniqueness first).
|
||||
*
|
||||
* Writes `FriendlyName` to the same string. That is the DISPLAY name — what the client
|
||||
* labels the room with — and it is only defaulted to `Name` on read
|
||||
* ({@link attachRoomDtoDefaults}), with `??=`, so a room whose blob has ever carried one
|
||||
* keeps it. Renaming without this leaves that room displaying its old name forever while
|
||||
* every name-keyed lookup uses the new one.
|
||||
*
|
||||
* The reference lets a creator set a display name apart from the unique `Name`; nothing
|
||||
* here exposes that, so the two are kept in step rather than allowed to diverge silently.
|
||||
*/
|
||||
/** Set a room's Name in place (the caller checks ownership + name uniqueness first). */
|
||||
export async function setRoomName(db: D1Database, roomId: number, name: string): Promise<void> {
|
||||
await db
|
||||
.prepare(
|
||||
"UPDATE room SET data = json_set(data, '$.Name', ?2, '$.FriendlyName', ?2) WHERE room_id = ?1"
|
||||
)
|
||||
.prepare("UPDATE room SET data = json_set(data, '$.Name', ?2) WHERE room_id = ?1")
|
||||
.bind(roomId, name)
|
||||
.run()
|
||||
}
|
||||
@@ -422,10 +531,12 @@ export async function updateRoomFields(
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a target account's room `Role` — updating their existing `Roles` entry or
|
||||
* appending a new one — and stamp `LastChangedByAccountId` with the editor. The
|
||||
* caller supplies the already-loaded room (after its owner/co-owner check) to avoid
|
||||
* a re-read; the whole room JSON is rewritten. Returns the updated room.
|
||||
* Set a target account's room `Role` — updating their existing `room_role` row or
|
||||
* inserting one — and stamp `last_changed_by` with the editor. One row per
|
||||
* (room, account), so the call is idempotent; a pending `invited_role` is left alone
|
||||
* (setting someone's role is not answering their invitation). The caller supplies the
|
||||
* already-loaded (hydrated) room, and gets it back with `Roles` updated to match the
|
||||
* table — the blob is not touched, roles don't live there.
|
||||
*/
|
||||
export async function setRoomRole(
|
||||
db: D1Database,
|
||||
@@ -435,7 +546,18 @@ export async function setRoomRole(
|
||||
changedByAccountId: number,
|
||||
room: Room
|
||||
): Promise<Room> {
|
||||
const roles = Array.isArray(room.Roles) ? (room.Roles as RoomRole[]) : []
|
||||
await db
|
||||
.prepare(
|
||||
`INSERT INTO room_role (room_id, account_id, role, last_changed_by, invited_role)
|
||||
VALUES (?1, ?2, ?3, ?4, 0)
|
||||
ON CONFLICT(room_id, account_id) DO UPDATE SET role = ?3, last_changed_by = ?4`
|
||||
)
|
||||
.bind(roomId, targetAccountId, role, changedByAccountId)
|
||||
.run()
|
||||
|
||||
// Mirror the write onto the hydrated room so the caller can serve it without a
|
||||
// re-read — the same entry shape {@link attachRoles} would produce.
|
||||
const roles = Array.isArray(room.Roles) ? [...(room.Roles as RoomRole[])] : []
|
||||
const existing = roles.find((r) => r.AccountId === targetAccountId)
|
||||
if (existing) {
|
||||
existing.Role = role
|
||||
@@ -448,12 +570,7 @@ export async function setRoomRole(
|
||||
InvitedRole: 0,
|
||||
})
|
||||
}
|
||||
const updated: Room = { ...room, Roles: roles }
|
||||
await db
|
||||
.prepare('UPDATE room SET data = ?2 WHERE room_id = ?1')
|
||||
.bind(roomId, serializeRoom(updated))
|
||||
.run()
|
||||
return updated
|
||||
return { ...room, Roles: roles }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1062,10 +1179,6 @@ const PUBLIC_WHERE = 'is_dorm IS NOT 1 AND accessibility = 1'
|
||||
* it is 0 for every room.
|
||||
* - `CurrentSnapshotId` — the room's published snapshot. Nothing takes snapshots, so it is
|
||||
* null, which is also what the reference serves for a room that has none.
|
||||
* - `FriendlyName` — the display name, which the reference lets a creator set apart from
|
||||
* the unique `Name`. Nothing sets one here, so it falls back to `Name`; it must never be
|
||||
* null, because the client labels a room from it and renders nothing for a room without
|
||||
* one.
|
||||
* - `CCU` — concurrent users. No live-population counter exists here, so it is null, which
|
||||
* is what the reference serves when it has no number rather than 0 (a 0 reads as "nobody
|
||||
* is in here" in the browse feeds).
|
||||
@@ -1076,7 +1189,6 @@ const PUBLIC_WHERE = 'is_dorm IS NOT 1 AND accessibility = 1'
|
||||
function attachRoomDtoDefaults(room: Room): void {
|
||||
room.BoostCount ??= 0
|
||||
room.CurrentSnapshotId ??= null
|
||||
room.FriendlyName ??= room.Name
|
||||
room.CCU ??= null
|
||||
}
|
||||
|
||||
@@ -1192,16 +1304,17 @@ const serializeSubRoom = (sub: SubRoom, roomId: number): string => {
|
||||
/**
|
||||
* Serialize a room for a full-blob write, dropping the parts that belong to another table
|
||||
* so the blob can never hold a stale copy: hydrated `SubRooms` (the `subroom` table's job),
|
||||
* `Tags` (the `room_tag` table's job — see {@link setRoomTags}), and the derived engagement
|
||||
* counters, which are zeroed rather than dropped so the key stays present (the
|
||||
* `interaction` table's job — see {@link attachStats}).
|
||||
* `Tags` (the `room_tag` table's job — see {@link setRoomTags}), `Roles` (the `room_role`
|
||||
* table's job — see {@link setRoomRole}), and the derived engagement counters, which are
|
||||
* zeroed rather than dropped so the key stays present (the `interaction` table's job —
|
||||
* see {@link attachStats}).
|
||||
*
|
||||
* Because `Tags` is dropped here, a write that means to CHANGE a room's tags has to write
|
||||
* the table itself; passing a room with a new `Tags` array through this silently discards
|
||||
* Because `Tags` and `Roles` are dropped here, a write that means to CHANGE them has to
|
||||
* write the table itself; passing a room with a new array through this silently discards
|
||||
* it.
|
||||
*/
|
||||
const serializeRoom = (room: Room): string => {
|
||||
const { SubRooms: _subRooms, Tags: _tags, Stats: stats, ...rest } = room
|
||||
const { SubRooms: _subRooms, Tags: _tags, Roles: _roles, Stats: stats, ...rest } = room
|
||||
return JSON.stringify({ ...rest, Stats: storedStats(stats) })
|
||||
}
|
||||
|
||||
@@ -1407,6 +1520,70 @@ function roomsByTagsQuery(tagSets: string[][], where = ''): { sql: string; binds
|
||||
return { sql: `SELECT ${ROOM_COLUMNS} FROM room r ${joins.join(' ')}${filter}`, binds }
|
||||
}
|
||||
|
||||
// ---- Room roles -----------------------------------------------------------
|
||||
// A room's roles live in `room_role`, not in the room blob (see ROOM_SCHEMA_DDL). The
|
||||
// blob is stripped on write and the array is re-attached on read, the same arrangement
|
||||
// as `room_tag`, so there is exactly one place a role is stored.
|
||||
|
||||
interface RoomRoleRow {
|
||||
room_id: number
|
||||
account_id: number
|
||||
role: number
|
||||
last_changed_by: number | null
|
||||
invited_role: number
|
||||
}
|
||||
|
||||
const toRoomRole = (row: RoomRoleRow): RoomRole => ({
|
||||
AccountId: row.account_id,
|
||||
Role: row.role,
|
||||
LastChangedByAccountId: row.last_changed_by,
|
||||
InvitedRole: row.invited_role,
|
||||
})
|
||||
|
||||
/** The given rooms' role entries, grouped by room id (one query for the batch). */
|
||||
async function rolesByRoom(db: D1Database, ids: number[]): Promise<Map<number, RoomRole[]>> {
|
||||
const byRoom = new Map<number, RoomRole[]>()
|
||||
if (ids.length === 0) return byRoom
|
||||
const results = await selectInChunks<RoomRoleRow>(
|
||||
db,
|
||||
ids,
|
||||
(placeholders) =>
|
||||
`SELECT room_id, account_id, role, last_changed_by, invited_role FROM room_role
|
||||
WHERE room_id IN (${placeholders}) ORDER BY account_id`
|
||||
)
|
||||
for (const row of results) {
|
||||
const list = byRoom.get(row.room_id) ?? []
|
||||
list.push(toRoomRole(row))
|
||||
byRoom.set(row.room_id, list)
|
||||
}
|
||||
return byRoom
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in each room's `Roles` from `room_role`. Every room ends up with the key PRESENT —
|
||||
* an empty array when it carries none — because {@link canManageRoom} and the client both
|
||||
* read it directly and the blob no longer supplies one.
|
||||
*/
|
||||
async function attachRoles(db: D1Database, rooms: Room[]): Promise<void> {
|
||||
const byRoom = await rolesByRoom(db, [...new Set(rooms.map(roomIdOf))])
|
||||
for (const room of rooms) room.Roles = byRoom.get(roomIdOf(room)) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed a brand-new room's creator entry (Role 255, `last_changed_by` NULL like the
|
||||
* imported creator rows) — the row every room minted by this module starts with.
|
||||
*/
|
||||
async function insertCreatorRole(db: D1Database, roomId: number, accountId: number): Promise<void> {
|
||||
await db
|
||||
.prepare(
|
||||
`INSERT INTO room_role (room_id, account_id, role, last_changed_by, invited_role)
|
||||
VALUES (?1, ?2, ?3, NULL, 0)
|
||||
ON CONFLICT(room_id, account_id) DO UPDATE SET role = ?3`
|
||||
)
|
||||
.bind(roomId, accountId, Role.Creator)
|
||||
.run()
|
||||
}
|
||||
|
||||
/** Parse subroom rows and resolve their `CurrentSave` in one batched query. */
|
||||
async function parseSubRoomRows(db: D1Database, rows: SubRoomRow[]): Promise<SubRoom[]> {
|
||||
const subs = rows.map(parseSubRoomRow)
|
||||
@@ -1556,7 +1733,8 @@ async function hydrateRoom(db: D1Database, room: Room | null): Promise<Room | nu
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate many rooms' `SubRooms`, `Tags` and derived `Stats` (one batched query each).
|
||||
* Hydrate many rooms' `SubRooms`, `Tags`, `Roles` and derived `Stats` (one batched query
|
||||
* each).
|
||||
*
|
||||
* `Tags` is re-attached even for the feeds that already did so before ranking
|
||||
* ({@link parseAllWithTags}) — it is one query for the page slice and it guarantees the key
|
||||
@@ -1570,6 +1748,7 @@ async function hydrateRooms(
|
||||
await Promise.all([
|
||||
attachSubRooms(db, rooms),
|
||||
attachTags(db, rooms),
|
||||
attachRoles(db, rooms),
|
||||
attachStats(db, rooms, stats),
|
||||
])
|
||||
return rooms
|
||||
@@ -1858,6 +2037,19 @@ export async function seedRoomWithSubRooms(db: D1Database, room: Room): Promise<
|
||||
if (Array.isArray(room.Tags) && room.Tags.length > 0) {
|
||||
await setRoomTags(db, roomId, room.Tags as RoomTag[])
|
||||
}
|
||||
// Same for `Roles` — mirrors 0017's backfill of `room_role` from the blobs.
|
||||
if (Array.isArray(room.Roles) && room.Roles.length > 0) {
|
||||
await db.batch(
|
||||
(room.Roles as RoomRole[]).map((r) =>
|
||||
db
|
||||
.prepare(
|
||||
`INSERT OR IGNORE INTO room_role (room_id, account_id, role, last_changed_by, invited_role)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)`
|
||||
)
|
||||
.bind(roomId, r.AccountId, r.Role ?? 0, r.LastChangedByAccountId, r.InvitedRole ?? 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
for (const sub of subRooms) {
|
||||
const subRoomId = Number(sub.SubRoomId)
|
||||
await db
|
||||
@@ -1899,6 +2091,8 @@ export async function deleteRoom(db: D1Database, roomId: number): Promise<void>
|
||||
// Tag rows outlive the blob otherwise, and would keep answering `#tag` searches and
|
||||
// category rows for a room nobody can open.
|
||||
db.prepare('DELETE FROM room_tag WHERE room_id = ?1').bind(roomId),
|
||||
// Role rows likewise — they'd keep the room in everyone's contributed list.
|
||||
db.prepare('DELETE FROM room_role WHERE room_id = ?1').bind(roomId),
|
||||
// Saves and permission overrides first — both are keyed by subroom, so they'd be
|
||||
// unreachable once the subrooms themselves are gone.
|
||||
db
|
||||
@@ -1989,9 +2183,9 @@ export async function countRoomsByCreator(db: D1Database, accountId: number): Pr
|
||||
* room the player made. A room matching BOTH halves appears once — the roles half is an
|
||||
* EXISTS, not a join.
|
||||
*
|
||||
* Roles live inside the room blob rather than in a table of their own, so the match is a
|
||||
* `json_each` over `$.Roles`. A room with no `Roles` key (or a null one) simply yields no
|
||||
* rows there rather than erroring, so it only reaches the list if the account created it.
|
||||
* The roles half matches on `room_role` (the table is authoritative — see ROOM_SCHEMA_DDL),
|
||||
* so it is an indexed probe per room rather than the `json_each` over every blob it was
|
||||
* when roles lived serialized in the room.
|
||||
*/
|
||||
export async function getContributedRooms(db: D1Database, accountId: number): Promise<Room[]> {
|
||||
const { results } = await db
|
||||
@@ -1999,8 +2193,8 @@ export async function getContributedRooms(db: D1Database, accountId: number): Pr
|
||||
`SELECT ${ROOM_COLUMNS} FROM room
|
||||
WHERE creator_account_id = ?1
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM json_each(room.data, '$.Roles') AS role
|
||||
WHERE json_extract(role.value, '$.AccountId') = ?1
|
||||
SELECT 1 FROM room_role WHERE room_role.room_id = room.room_id
|
||||
AND room_role.account_id = ?1
|
||||
)`
|
||||
)
|
||||
.bind(accountId)
|
||||
@@ -2839,9 +3033,11 @@ export async function getOrCreateDormRoom(db: D1Database, accountId: number): Pr
|
||||
Stats: storedStats(template?.Stats),
|
||||
CreatedAt: new Date().toISOString(),
|
||||
}
|
||||
// serializeRoom drops any SubRooms carried over from the template; the dorm's own
|
||||
// subroom is inserted into the subroom table below with a fresh globally-unique id.
|
||||
// serializeRoom drops any SubRooms carried over from the template (and Roles — those
|
||||
// go to their own table); the dorm's own subroom is inserted into the subroom table
|
||||
// below with a fresh globally-unique id.
|
||||
await db.prepare('INSERT INTO room (data) VALUES (?1)').bind(serializeRoom(room)).run()
|
||||
await insertCreatorRole(db, roomId, accountId)
|
||||
const subRoom = await insertSubRoom(db, roomId, { ...templateSub, CreatorAccountId: accountId })
|
||||
room.SubRooms = [subRoom]
|
||||
// The template carries these (it was parsed), but a dorm minted without one wouldn't.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Server statistics sampled over time (`stat` table). The `match` presence cron
|
||||
* records one `online` sample per run — the count of live `presence` rows once the
|
||||
* expired ones are swept. Migration: apps/match/migrations/0002_stat.sql.
|
||||
*/
|
||||
|
||||
/** Schema DDL (mirror of apps/match/migrations/0002_stat.sql). */
|
||||
export const STAT_SCHEMA_DDL: string[] = [
|
||||
`CREATE TABLE IF NOT EXISTS stat (
|
||||
stat_type TEXT NOT NULL,
|
||||
value INTEGER NOT NULL,
|
||||
datetime TEXT NOT NULL
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_stat_type_datetime ON stat (stat_type, datetime)`,
|
||||
]
|
||||
|
||||
export interface StatRow {
|
||||
statType: string
|
||||
value: number
|
||||
datetime: string
|
||||
}
|
||||
|
||||
/** Record one sample of `statType`, stamped with the current UTC time (ISO-8601). */
|
||||
export async function recordStat(
|
||||
db: D1Database,
|
||||
statType: string,
|
||||
value: number,
|
||||
now: Date = new Date()
|
||||
): Promise<void> {
|
||||
await db
|
||||
.prepare('INSERT INTO stat (stat_type, value, datetime) VALUES (?1, ?2, ?3)')
|
||||
.bind(statType, value, now.toISOString())
|
||||
.run()
|
||||
}
|
||||
|
||||
/** Samples of `statType`, oldest first. */
|
||||
export async function getStats(db: D1Database, statType: string, limit = 1000): Promise<StatRow[]> {
|
||||
const { results } = await db
|
||||
.prepare(
|
||||
'SELECT stat_type, value, datetime FROM stat WHERE stat_type = ?1 ORDER BY datetime ASC LIMIT ?2'
|
||||
)
|
||||
.bind(statType, limit)
|
||||
.all<{ stat_type: string; value: number; datetime: string }>()
|
||||
return results.map((r) => ({ statType: r.stat_type, value: r.value, datetime: r.datetime }))
|
||||
}
|
||||
@@ -61,6 +61,15 @@ export const MIN_INVENTION_NAME_LENGTH = 3
|
||||
export const MAX_INVENTION_NAME_LENGTH = 24
|
||||
export const MAX_INVENTION_DESCRIPTION_LENGTH = 512
|
||||
|
||||
/**
|
||||
* The long description — the blurb on an invention's detail page, as opposed to the one
|
||||
* line that fits under a browse tile. The client sends it on `v9/save` and edits it
|
||||
* through `v2/metadata`, and how long it lets one get has not been pinned down, so the cap
|
||||
* here is this server's own: generous enough that no real blurb hits it, small enough that
|
||||
* a record stays a record. It is checked only when a caller actually sends one.
|
||||
*/
|
||||
export const MAX_INVENTION_LONG_DESCRIPTION_LENGTH = 4096
|
||||
|
||||
/**
|
||||
* One invention tag. Short and letters-only because tags are a controlled vocabulary the
|
||||
* browse chips are derived from (see `getInventionTagFilters`) — a tag with digits,
|
||||
@@ -180,6 +189,14 @@ export function inventionDescriptionRejection(value: string): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
/** Why a long description can't be stored, or null when it can. */
|
||||
export function inventionLongDescriptionRejection(value: string): string | null {
|
||||
if (glyphLength(value) > MAX_INVENTION_LONG_DESCRIPTION_LENGTH) {
|
||||
return `Invention long descriptions can be at most ${MAX_INVENTION_LONG_DESCRIPTION_LENGTH} characters.`
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Why an invention tag is unacceptable, or `null` when it's fine. Pass the NORMALIZED
|
||||
* tag (trimmed and lowercased, as `setInventionTags` stores it) — checking what was typed
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export {
|
||||
validateAndGetAccountId,
|
||||
validateAndGetPlus,
|
||||
validateAndGetRoles,
|
||||
validateAndGetVersion,
|
||||
generateToken,
|
||||
|
||||
+40
-1
@@ -82,6 +82,30 @@ export async function validateAndGetRoles(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a request's bearer token says the caller has Rec Room Plus — the `rn.plus`
|
||||
* claim stamped by {@link generateToken} from `account.hasPlus`. This is the ONE way Plus
|
||||
* is decided (see `econ`'s `isSubscriber`); nothing re-reads the account for it, which is
|
||||
* why a freshly-claimed player must sign in again before it applies.
|
||||
*
|
||||
* False for a missing, malformed or expired token, and false for a valid token that
|
||||
* simply carries no claim — the two are not worth telling apart, since neither is a
|
||||
* subscriber. Only a literal `true` counts, so a token carrying some other value in that
|
||||
* key can't read as Plus.
|
||||
*/
|
||||
export async function validateAndGetPlus(request: Request, secret: string): Promise<boolean> {
|
||||
const authHeader = request.headers.get('Authorization')
|
||||
if (!authHeader || !authHeader.toLowerCase().startsWith('bearer ')) return false
|
||||
|
||||
const token = authHeader.slice('bearer '.length)
|
||||
try {
|
||||
const payload = await verify(token, secret, 'HS256') // checks exp/nbf/signature
|
||||
return payload['rn.plus'] === true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a request's bearer token and return its `rn.ver` claim — the game build the
|
||||
* caller posted to `/connect/token`, stamped by {@link generateToken}. `null` when the
|
||||
@@ -189,7 +213,8 @@ export async function generateToken(
|
||||
secret: string,
|
||||
extraRoles: string[] = [],
|
||||
privileges: string[] = [],
|
||||
version: string = GAME_VERSION
|
||||
version: string = GAME_VERSION,
|
||||
hasPlus = false
|
||||
): Promise<string> {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
// The client reads `role`/`scope` (and expects a well-formed iss/aud) to
|
||||
@@ -220,6 +245,20 @@ export async function generateToken(
|
||||
// `scope`. Omitted entirely when empty, so an unrestricted token is byte-for-byte
|
||||
// what it was before privileges existed.
|
||||
...(privileges.length > 0 ? { 'rn.privilege': privileges } : {}),
|
||||
// Whether the account has Rec Room Plus (`account.hasPlus`) — a CLAIM, like
|
||||
// `rn.privilege` and for the same reason: it is ours, the client has never heard of
|
||||
// it, and `scope` is a fixed list the client parses. `econ` reads it to answer the
|
||||
// CampusCard lookup and to price the subscriber discount, which is the whole point
|
||||
// of carrying it here: those calls then need no database read at all.
|
||||
//
|
||||
// Omitted when false, so a non-subscriber's token is byte-for-byte what it was
|
||||
// before Plus existed, and `validateAndGetPlus` reads an absent claim as "no Plus".
|
||||
//
|
||||
// STAMPED AT LOGIN, so it is only as fresh as the token: a player who claims Plus on
|
||||
// the website has to sign in again (and restart the game) before it takes effect.
|
||||
// Tokens last a day and the client does not refresh them — see TOKEN_TTL_SECONDS —
|
||||
// so that wait is real, and it is the accepted trade for making the check free.
|
||||
...(hasPlus ? { 'rn.plus': true } : {}),
|
||||
scope: TOKEN_SCOPES,
|
||||
jti: crypto.randomUUID(),
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { D1ExecResult } from '../d1'
|
||||
* runx admin clear-password --username alice [--remote]
|
||||
* runx admin lookup --username alice [--remote]
|
||||
* runx admin grant-developer --account 1 [--revoke] [--remote]
|
||||
* runx admin grant-plus --username alice [--revoke] [--remote]
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -123,16 +124,21 @@ const clearPassword = new Command('clear-password')
|
||||
})
|
||||
|
||||
/**
|
||||
* Build a `grant-<role>` command that toggles a boolean role flag on the account
|
||||
* blob. `jsonKey` is the account field (e.g. `isDeveloper`) — a fixed literal, not
|
||||
* user input. Both the /role/:role lookup and the token's `role` claim read it.
|
||||
* Build a `grant-<thing>` command that toggles a boolean flag on the account blob.
|
||||
* `jsonKey` is the account field (e.g. `isDeveloper`) — a fixed literal, not user input.
|
||||
*
|
||||
* `noun` is what the flag IS, and it is not always "role": the role flags feed the
|
||||
* /role/:role lookup and the token's `role` claim, while `hasPlus` is an entitlement that
|
||||
* rides on its own `rn.plus` claim and confers no role at all. Getting that word right in
|
||||
* the output is the difference between an operator believing they granted a staff power
|
||||
* and knowing they granted a subscription.
|
||||
*/
|
||||
function grantRoleCommand(name: string, jsonKey: string, roleLabel: string) {
|
||||
function grantRoleCommand(name: string, jsonKey: string, roleLabel: string, noun = 'role') {
|
||||
return new Command(name)
|
||||
.description(`Grant (or, with --revoke, remove) the ${roleLabel} role on an account`)
|
||||
.description(`Grant (or, with --revoke, remove) ${roleLabel} on an account`)
|
||||
.option('--account <id>', 'Account id to target')
|
||||
.option('--username <name>', 'Username to target (case-insensitive)')
|
||||
.option('--revoke', `Remove the ${roleLabel} role instead of granting it`, false)
|
||||
.option('--revoke', `Remove ${roleLabel} instead of granting it`, false)
|
||||
.option('--local', 'Target the local dev database (the default).', false)
|
||||
.option('--remote', 'Target the deployed database instead of the local dev database.', false)
|
||||
.action(async (opts) => {
|
||||
@@ -141,10 +147,10 @@ function grantRoleCommand(name: string, jsonKey: string, roleLabel: string) {
|
||||
const value = opts.revoke ? 'false' : 'true'
|
||||
const sql = `UPDATE account SET data = json_set(data, '$.${jsonKey}', json('${value}')) WHERE ${where} RETURNING account_id`
|
||||
const verb = opts.revoke ? 'Revoking' : 'Granting'
|
||||
console.log(`${verb} ${roleLabel} role for ${label} on ${target(remote)}`)
|
||||
console.log(`${verb} ${roleLabel} ${noun} for ${label} on ${target(remote)}`)
|
||||
assertMatched(await execSql(sql, remote), label)
|
||||
console.log(
|
||||
chalk.green(`✓ ${roleLabel} role ${opts.revoke ? 'revoked' : 'granted'} for ${label}`)
|
||||
chalk.green(`✓ ${roleLabel} ${noun} ${opts.revoke ? 'revoked' : 'granted'} for ${label}`)
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -152,6 +158,21 @@ function grantRoleCommand(name: string, jsonKey: string, roleLabel: string) {
|
||||
const grantDeveloper = grantRoleCommand('grant-developer', 'isDeveloper', 'developer')
|
||||
const grantModerator = grantRoleCommand('grant-moderator', 'isModerator', 'moderator')
|
||||
|
||||
/**
|
||||
* Rec Room Plus, the account's `hasPlus` flag. Players normally get it themselves by
|
||||
* claiming a Discord role on the website; this is the operator's way in — and the ONLY
|
||||
* one, since the `developer` role deliberately no longer confers Plus.
|
||||
*
|
||||
* Granting does not take effect until the account's NEXT login: `auth` stamps `hasPlus`
|
||||
* into the token as `rn.plus` when it mints one, and `econ` reads nothing else. Tokens
|
||||
* last a day and the client never refreshes them, so tell the player to restart the game
|
||||
* and sign in again.
|
||||
*
|
||||
* Revoking has the same lag in reverse — a player keeps Plus until their current token
|
||||
* expires. It is not a way to cut someone off immediately.
|
||||
*/
|
||||
const grantPlus = grantRoleCommand('grant-plus', 'hasPlus', 'Rec Room Plus', 'subscription')
|
||||
|
||||
const lookup = new Command('lookup')
|
||||
.description('Print an account by id or username')
|
||||
.option('--account <id>', 'Account id to look up')
|
||||
@@ -202,6 +223,7 @@ export const adminCmd = new Command('admin')
|
||||
.addCommand(clearPassword)
|
||||
.addCommand(grantDeveloper)
|
||||
.addCommand(grantModerator)
|
||||
.addCommand(grantPlus)
|
||||
.addCommand(lookup)
|
||||
.addHelpText(
|
||||
'after',
|
||||
@@ -216,5 +238,6 @@ Examples:
|
||||
$ runx admin clear-password --username alice
|
||||
$ runx admin grant-developer --account 1 [--revoke]
|
||||
$ runx admin grant-moderator --username alice --remote
|
||||
$ runx admin grant-plus --username alice # Rec Room Plus; takes effect next login
|
||||
$ runx admin lookup --username alice --remote`
|
||||
)
|
||||
|
||||
Generated
+3
@@ -1243,6 +1243,9 @@ importers:
|
||||
'@repo/hono-helpers':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/hono-helpers
|
||||
'@repo/jwt':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/jwt
|
||||
'@scalar/api-reference':
|
||||
specifier: 1.63.0
|
||||
version: 1.63.0(tailwindcss@4.3.3)(typescript@6.0.3)(zod@4.4.3)
|
||||
|
||||
+24494
@@ -0,0 +1,24494 @@
|
||||
{
|
||||
"feature_gates": {
|
||||
"B5laBualtDRuj6P+QAeRjFEYLauCuorRXozziGDJbHw=": {
|
||||
"name": "B5laBualtDRuj6P+QAeRjFEYLauCuorRXozziGDJbHw=",
|
||||
"value": false,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xsqNrxopyX9VjvhDELN7RtF2QqDb5gwGyJ50YWLJeTA=": {
|
||||
"name": "xsqNrxopyX9VjvhDELN7RtF2QqDb5gwGyJ50YWLJeTA=",
|
||||
"value": false,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"IQEy+nwmKfXw4qCkVBhFPnHeu4/FC6UtCwA63gmtWSw=": {
|
||||
"name": "IQEy+nwmKfXw4qCkVBhFPnHeu4/FC6UtCwA63gmtWSw=",
|
||||
"value": false,
|
||||
"rule_id": "295c0Rt8qSUEKheiq1Ycm8",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"FK+XIh76FOUw4GkuqMQewf1QQfRoAbY7Xaz89pnSs+Y=": {
|
||||
"name": "FK+XIh76FOUw4GkuqMQewf1QQfRoAbY7Xaz89pnSs+Y=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"/rD5SEjOjhJyyYXj5JZbOIq+QAcnXT6LTT7k1QixJZ0=": {
|
||||
"name": "/rD5SEjOjhJyyYXj5JZbOIq+QAcnXT6LTT7k1QixJZ0=",
|
||||
"value": false,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ZXdRgKCuhjr9Fb3C9q+rMUfwL9IeEh+9/Mjd8k5/WI4=": {
|
||||
"name": "ZXdRgKCuhjr9Fb3C9q+rMUfwL9IeEh+9/Mjd8k5/WI4=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"8QNNjAH/6Jy1jDAdQcwIQUKIvcyX7GBu4smapmvL/QU=": {
|
||||
"name": "8QNNjAH/6Jy1jDAdQcwIQUKIvcyX7GBu4smapmvL/QU=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7f+USfi740/Q1+nYfMN5wd3bEfKbIJQq6Pj7uTV9kWI=": {
|
||||
"name": "7f+USfi740/Q1+nYfMN5wd3bEfKbIJQq6Pj7uTV9kWI=",
|
||||
"value": true,
|
||||
"rule_id": "19g388pu2dGjUSfuPX2BrR",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"YKNzMvi/E0QOqRWsRU2HZ7Q8UNNzyzPjIAJBZAegMQQ=": {
|
||||
"name": "YKNzMvi/E0QOqRWsRU2HZ7Q8UNNzyzPjIAJBZAegMQQ=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"y19ILjKrUeroOxwu/tKDFZPoJa+q46Q3Rzhwo77f4kw=": {
|
||||
"name": "y19ILjKrUeroOxwu/tKDFZPoJa+q46Q3Rzhwo77f4kw=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"VGqtFv6RdkOD2aAufilGjo0vZCA56YsiAIrEIQfA9bs=": {
|
||||
"name": "VGqtFv6RdkOD2aAufilGjo0vZCA56YsiAIrEIQfA9bs=",
|
||||
"value": true,
|
||||
"rule_id": "2fNnRcH7KFuAir6wVPTVNg",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hO8A2v0izRLG/TrVS9KaAtCRkHrxy0zjepEqm2Wa7M8=": {
|
||||
"name": "hO8A2v0izRLG/TrVS9KaAtCRkHrxy0zjepEqm2Wa7M8=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QpjWqajI6Vpg1yP7V5GOiXgXcG2nI4wUaG5WDpznKek=": {
|
||||
"name": "QpjWqajI6Vpg1yP7V5GOiXgXcG2nI4wUaG5WDpznKek=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+ghhAiI/bdBmS5fpZmkU5mW86yzXrt/Ct8FreS6iGsE=": {
|
||||
"name": "+ghhAiI/bdBmS5fpZmkU5mW86yzXrt/Ct8FreS6iGsE=",
|
||||
"value": true,
|
||||
"rule_id": "4rF5VIQvgbpFBvw9004os4",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"sLKQ22VnlOJvZWAs3LwmyN6u2ouZ3aDxglSELEu6OAA=": {
|
||||
"name": "sLKQ22VnlOJvZWAs3LwmyN6u2ouZ3aDxglSELEu6OAA=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"/y81Qe7uKUK5D15beqnnxzBuTfSZ8wPfZN8d4BPXeVE=": {
|
||||
"name": "/y81Qe7uKUK5D15beqnnxzBuTfSZ8wPfZN8d4BPXeVE=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JEJg8Colzb+nlAsYaUvZwkY6g6kuWQoeX458PB6Xagw=": {
|
||||
"name": "JEJg8Colzb+nlAsYaUvZwkY6g6kuWQoeX458PB6Xagw=",
|
||||
"value": true,
|
||||
"rule_id": "NeJLCAEFXeb26oIqJNUlY",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"v1DOMT2rTpZ1iHRq1uH3Do5cMDp1zi5x4OvAXU717lE=": {
|
||||
"name": "v1DOMT2rTpZ1iHRq1uH3Do5cMDp1zi5x4OvAXU717lE=",
|
||||
"value": true,
|
||||
"rule_id": "6LjBNqu4aSaElhuM3hYyuT",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"vKvXNjyo+aDlJX6MLQAez1bADPkM4uO0OpXL+IIuI+k=": {
|
||||
"name": "vKvXNjyo+aDlJX6MLQAez1bADPkM4uO0OpXL+IIuI+k=",
|
||||
"value": true,
|
||||
"rule_id": "41CofcjQyyCKk5cNmM9fk",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"8JVnJnUMp4fZQ7s8qLoJi9v/NBUSUk67HIh8ofYt/8E=": {
|
||||
"name": "8JVnJnUMp4fZQ7s8qLoJi9v/NBUSUk67HIh8ofYt/8E=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"i6fuNY5AURom5EPOuoV8rEoJMRCcVfOHJIdXUp5AS0w=": {
|
||||
"name": "i6fuNY5AURom5EPOuoV8rEoJMRCcVfOHJIdXUp5AS0w=",
|
||||
"value": true,
|
||||
"rule_id": "3TAX0JJHTGfnKKVDH5faWp",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"nuqBQiKPtwo9UOebsupz1tI3PXJEjd6+i0OCx+9NAUk=": {
|
||||
"name": "nuqBQiKPtwo9UOebsupz1tI3PXJEjd6+i0OCx+9NAUk=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"2cJ322wGODB2fu7Jo0h9cR3Raedh499wUQFSZH5naSo=": {
|
||||
"name": "2cJ322wGODB2fu7Jo0h9cR3Raedh499wUQFSZH5naSo=",
|
||||
"value": true,
|
||||
"rule_id": "4y2qrbnwfrWAXU9ARwrm56",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"3myiw8W72gNFhPHkI0q0UxXERtZWTQ83lnK8BuLIflE=": {
|
||||
"name": "3myiw8W72gNFhPHkI0q0UxXERtZWTQ83lnK8BuLIflE=",
|
||||
"value": true,
|
||||
"rule_id": "VQecv3QkDzY7riOlGpWDg",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"aDcEVBe+DufjOhyxR8XBNOSBj8W92JZ5XQW3ANwT5Ao=": {
|
||||
"name": "aDcEVBe+DufjOhyxR8XBNOSBj8W92JZ5XQW3ANwT5Ao=",
|
||||
"value": true,
|
||||
"rule_id": "2vMR8LtXkPRmGtgayCXCGO",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ImDNWaPvT8D8fBR0doZVTlLWRe82FDwb5Wk8lWWUCxA=": {
|
||||
"name": "ImDNWaPvT8D8fBR0doZVTlLWRe82FDwb5Wk8lWWUCxA=",
|
||||
"value": true,
|
||||
"rule_id": "17lJMFU5gZOFJUlCEk3GOw",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "2338584192",
|
||||
"gateValue": "true",
|
||||
"ruleID": "6ynlPE1rn0ChZx3Ob2uT1j"
|
||||
}
|
||||
]
|
||||
},
|
||||
"UqJmttoyWN69S41xR/7g8ty7U9/lBFMtjyvDCcO8mDk=": {
|
||||
"name": "UqJmttoyWN69S41xR/7g8ty7U9/lBFMtjyvDCcO8mDk=",
|
||||
"value": false,
|
||||
"rule_id": "1HS9jwxKf6Nfw2Rn0bWZoC:0.00:2",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"YekE6meB0QxXnoaqpL0tOEbeNetvmEVFgsnIQwFlTPE=": {
|
||||
"name": "YekE6meB0QxXnoaqpL0tOEbeNetvmEVFgsnIQwFlTPE=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"lAYKV5/y2Qk/V72I3YSq910vYDg9i8UV8a9Jm5cKg0M=": {
|
||||
"name": "lAYKV5/y2Qk/V72I3YSq910vYDg9i8UV8a9Jm5cKg0M=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7/fpJ/FqtVwWR4E4A/BkG476VkX+fCn9L2BYQmggTAA=": {
|
||||
"name": "7/fpJ/FqtVwWR4E4A/BkG476VkX+fCn9L2BYQmggTAA=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"k+GnHO9aMqeTMHMCN4sV3LnZvVo1CrliqWmZrWb3EnE=": {
|
||||
"name": "k+GnHO9aMqeTMHMCN4sV3LnZvVo1CrliqWmZrWb3EnE=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"HscNJPF4DoE+XQqWSBCkZ/Zl/gfD2oTop1d/BNa9p+0=": {
|
||||
"name": "HscNJPF4DoE+XQqWSBCkZ/Zl/gfD2oTop1d/BNa9p+0=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"aByUSaOkHN38s0Y5fq2UMDAfBkY1GT9fog4Z5oladhU=": {
|
||||
"name": "aByUSaOkHN38s0Y5fq2UMDAfBkY1GT9fog4Z5oladhU=",
|
||||
"value": true,
|
||||
"rule_id": "3fxFQpebFUB6BMSlT8imwu",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"uEGNDmAAOPCY4SZRd2jGvVGeWftnFkVeZJBCLlH47Xg=": {
|
||||
"name": "uEGNDmAAOPCY4SZRd2jGvVGeWftnFkVeZJBCLlH47Xg=",
|
||||
"value": true,
|
||||
"rule_id": "3CtFMPDKBmIKH8GIc5bz6g",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "2338584192",
|
||||
"gateValue": "true",
|
||||
"ruleID": "6ynlPE1rn0ChZx3Ob2uT1j"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pV0Vs7cZfBtF/W2VpiOkiWT23WVl1z79HiejR1z31Og=": {
|
||||
"name": "pV0Vs7cZfBtF/W2VpiOkiWT23WVl1z79HiejR1z31Og=",
|
||||
"value": true,
|
||||
"rule_id": "2FRs1xg5FsBsh7WbB6Pux4",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hT4+XJMsEVC07ct6Rngq5d4pyV9SW9c92yFohG7K24g=": {
|
||||
"name": "hT4+XJMsEVC07ct6Rngq5d4pyV9SW9c92yFohG7K24g=",
|
||||
"value": false,
|
||||
"rule_id": "7c4QL3WEghkDttWgDN2m2L:0.00:1",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"CX3JbgRODvhIumahma/GXmnPajR8/NdjLiDxPhbv0yI=": {
|
||||
"name": "CX3JbgRODvhIumahma/GXmnPajR8/NdjLiDxPhbv0yI=",
|
||||
"value": true,
|
||||
"rule_id": "3RNbXTrAOxK52iM2qm5qwN",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"93NRtvxy4Rbh9lEP9yNqJrM05uzZS1VaXl9U7Pjfz68=": {
|
||||
"name": "93NRtvxy4Rbh9lEP9yNqJrM05uzZS1VaXl9U7Pjfz68=",
|
||||
"value": true,
|
||||
"rule_id": "SsxAAB2YkVNBPbFv7cxzs",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "2338584192",
|
||||
"gateValue": "true",
|
||||
"ruleID": "6ynlPE1rn0ChZx3Ob2uT1j"
|
||||
}
|
||||
]
|
||||
},
|
||||
"xgv1N9XwDYKdaShKLmKqf5AEMIYhbpgZr0URM2uvlwQ=": {
|
||||
"name": "xgv1N9XwDYKdaShKLmKqf5AEMIYhbpgZr0URM2uvlwQ=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"kSm7cJNexoR0xwyUGzEY9Ko0org3ZIbEuhEwPqQ0VN4=": {
|
||||
"name": "kSm7cJNexoR0xwyUGzEY9Ko0org3ZIbEuhEwPqQ0VN4=",
|
||||
"value": true,
|
||||
"rule_id": "7y5b0iUlUn0jaIIB2OG811",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"YkYJqqCglpUXdraEn/MZ8D6zG3cFdJtwFza8/cWP/RM=": {
|
||||
"name": "YkYJqqCglpUXdraEn/MZ8D6zG3cFdJtwFza8/cWP/RM=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Mjt5XLRntqovraoI0f+ovXlwIKjVB72cSoIWEXR3YDU=": {
|
||||
"name": "Mjt5XLRntqovraoI0f+ovXlwIKjVB72cSoIWEXR3YDU=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+ffnXmOI2k0GY4ceXYHu8o9oQXiZxt/UUr9tcgYaQnA=": {
|
||||
"name": "+ffnXmOI2k0GY4ceXYHu8o9oQXiZxt/UUr9tcgYaQnA=",
|
||||
"value": true,
|
||||
"rule_id": "4T7vjxu0Ujr49LnZaKQMHu:100.00:1",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"K/cGmZM/TBW+XVqUqWSDpaMbUxL3PG3Hny89CuCC15Y=": {
|
||||
"name": "K/cGmZM/TBW+XVqUqWSDpaMbUxL3PG3Hny89CuCC15Y=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ibZR1pjVgQ+ngME8Z79uPB02EICk2UWKMTdT+a/7X8M=": {
|
||||
"name": "ibZR1pjVgQ+ngME8Z79uPB02EICk2UWKMTdT+a/7X8M=",
|
||||
"value": true,
|
||||
"rule_id": "48KXHvr8LOEjYfRal6DBCG",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"gTbk8z8BDTS3sPf2P2cjjVMmrB09/m0NbO4H+kMVtoo=": {
|
||||
"name": "gTbk8z8BDTS3sPf2P2cjjVMmrB09/m0NbO4H+kMVtoo=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"s8czAl8N9Tl9x3lBsPEcJmkrqgRTGihmbB1DCcmeWb4=": {
|
||||
"name": "s8czAl8N9Tl9x3lBsPEcJmkrqgRTGihmbB1DCcmeWb4=",
|
||||
"value": false,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ooWPMX+nepHZMpT1SkZL5B2Q0pw76PfeHUKWAHBfNdE=": {
|
||||
"name": "ooWPMX+nepHZMpT1SkZL5B2Q0pw76PfeHUKWAHBfNdE=",
|
||||
"value": true,
|
||||
"rule_id": "jxdFIRuHhxnSfXoPtyEEL",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ltUWPvuzJajzNPLYMnDniGKUFKdfMr4VNK5mMtflaps=": {
|
||||
"name": "ltUWPvuzJajzNPLYMnDniGKUFKdfMr4VNK5mMtflaps=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+lphqsrtsJMAdYt1xC6A3x6fFuZh1pql6QPCmiqpGNY=": {
|
||||
"name": "+lphqsrtsJMAdYt1xC6A3x6fFuZh1pql6QPCmiqpGNY=",
|
||||
"value": true,
|
||||
"rule_id": "4gQtmddP2Gauv81AqWUi7a",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"4HCcyg60Ky2lUb9Ei4zlVFRusbihkv9W6UYHy0K90Ac=": {
|
||||
"name": "4HCcyg60Ky2lUb9Ei4zlVFRusbihkv9W6UYHy0K90Ac=",
|
||||
"value": true,
|
||||
"rule_id": "1VpLvSsppJityYlbf2J0ms",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"F3+VcrsatRfVdp9B0M9wf4FpFC4YGTNRgkSelhVl6YI=": {
|
||||
"name": "F3+VcrsatRfVdp9B0M9wf4FpFC4YGTNRgkSelhVl6YI=",
|
||||
"value": true,
|
||||
"rule_id": "6ynlPE1rn0ChZx3Ob2uT1j",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"XjUC7f2sxWBOP6LjFb9UR0xzPF7ScptUbVgpA+wUjyc=": {
|
||||
"name": "XjUC7f2sxWBOP6LjFb9UR0xzPF7ScptUbVgpA+wUjyc=",
|
||||
"value": true,
|
||||
"rule_id": "5wzMtLjSXYmWPzy1fJq0BN",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"3g45rm5ENTWOy9O1o2T0rBj/6qgb5+dz5ZeUwBURJRU=": {
|
||||
"name": "3g45rm5ENTWOy9O1o2T0rBj/6qgb5+dz5ZeUwBURJRU=",
|
||||
"value": false,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"P2MEwSH9y33VpkJ/CdiL4P+idshDTVwC9aA96/1vKQw=": {
|
||||
"name": "P2MEwSH9y33VpkJ/CdiL4P+idshDTVwC9aA96/1vKQw=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tuYQMbLddGOEqd/N4mwKlNoIUU6dBIiSIo4ymNdhR04=": {
|
||||
"name": "tuYQMbLddGOEqd/N4mwKlNoIUU6dBIiSIo4ymNdhR04=",
|
||||
"value": true,
|
||||
"rule_id": "1I6hD6zvoHsMWzCkOhEZ0O",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"2Tolo1SRZmjMptp47JkD71tjoYypCvADiKuUb8vmtf0=": {
|
||||
"name": "2Tolo1SRZmjMptp47JkD71tjoYypCvADiKuUb8vmtf0=",
|
||||
"value": false,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"x7pxgQA7kUssYewSBS0xIUa3u02/WkzRhtf0CQB0f2s=": {
|
||||
"name": "x7pxgQA7kUssYewSBS0xIUa3u02/WkzRhtf0CQB0f2s=",
|
||||
"value": false,
|
||||
"rule_id": "56L0gAQ3oobID0zBE44dT5:0.00:1",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"s1NnVmZyQQFKIOeYVp2MQxxoX1ERb/xX/E/DMPodcv4=": {
|
||||
"name": "s1NnVmZyQQFKIOeYVp2MQxxoX1ERb/xX/E/DMPodcv4=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"8BNCbEQ9iIpg+ZRPlLqq38OX8Pvhp2Tlc8GWBTT65XI=": {
|
||||
"name": "8BNCbEQ9iIpg+ZRPlLqq38OX8Pvhp2Tlc8GWBTT65XI=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bJRL6+8XJKavjIvBlHKAfCI1Dg5IKqJq2VuHXybO2gM=": {
|
||||
"name": "bJRL6+8XJKavjIvBlHKAfCI1Dg5IKqJq2VuHXybO2gM=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"FmMTbIgp9OTiZ+5c7BdYn07WPzFJ9J8fF+i+xODvqHQ=": {
|
||||
"name": "FmMTbIgp9OTiZ+5c7BdYn07WPzFJ9J8fF+i+xODvqHQ=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"H4yVxYd/NPxrvnoUcHGHtIpbYSVmnfzSXFKV9jaW7RI=": {
|
||||
"name": "H4yVxYd/NPxrvnoUcHGHtIpbYSVmnfzSXFKV9jaW7RI=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+XAzQPU7XpDe/IT8eEsGSYKf5vqZOgc314NxV0MyoWs=": {
|
||||
"name": "+XAzQPU7XpDe/IT8eEsGSYKf5vqZOgc314NxV0MyoWs=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fGR2nfiGVeQr+vHtLHIC6T+c8+ozzSYISeJAkVJXJo0=": {
|
||||
"name": "fGR2nfiGVeQr+vHtLHIC6T+c8+ozzSYISeJAkVJXJo0=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0bO1/uH9ruEE6hf4zVz1UChder7LK4obyZCRNW8Vti0=": {
|
||||
"name": "0bO1/uH9ruEE6hf4zVz1UChder7LK4obyZCRNW8Vti0=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"sfgqwMusaqTClLpyhFM53hyyK9Kv9N3EgwEN0McLBJE=": {
|
||||
"name": "sfgqwMusaqTClLpyhFM53hyyK9Kv9N3EgwEN0McLBJE=",
|
||||
"value": false,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ulzn63855nmzhFge4PcX0E+cOQSkCZLcpGBx2AIQlOY=": {
|
||||
"name": "Ulzn63855nmzhFge4PcX0E+cOQSkCZLcpGBx2AIQlOY=",
|
||||
"value": true,
|
||||
"rule_id": "4CJEjMoIXOYXetn2okwp9E:50.00:10",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"RyBT/RU6wdg49hTSfTtgcjqvU76d4Xorve4T5d6hHIk=": {
|
||||
"name": "RyBT/RU6wdg49hTSfTtgcjqvU76d4Xorve4T5d6hHIk=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Yfjf+bueXVLbh8RBbqEILr48Lh4ZFTGIDHdzSifsXQU=": {
|
||||
"name": "Yfjf+bueXVLbh8RBbqEILr48Lh4ZFTGIDHdzSifsXQU=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"MCoPJTNvmCXuy/pt9LxkphvSpMP35VJBaWR39M1oVxI=": {
|
||||
"name": "MCoPJTNvmCXuy/pt9LxkphvSpMP35VJBaWR39M1oVxI=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"IKgpnTSFoqP3XiT+o5sqjciQq+WXw4BEcvnvJ+3kECk=": {
|
||||
"name": "IKgpnTSFoqP3XiT+o5sqjciQq+WXw4BEcvnvJ+3kECk=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bQ/wnBRTxim0fBNszWoLQtOjUKILgOjG5rTa0L2kO3k=": {
|
||||
"name": "bQ/wnBRTxim0fBNszWoLQtOjUKILgOjG5rTa0L2kO3k=",
|
||||
"value": false,
|
||||
"rule_id": "default",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"h25tMCCn2DL+Zi8hCNJ0BdiUm8dnaSVI1WUpfrWkTqI=": {
|
||||
"name": "h25tMCCn2DL+Zi8hCNJ0BdiUm8dnaSVI1WUpfrWkTqI=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"IjD171leXnWNBCWoGYrWEO+bbkttsyZiWVIbfkppYfY=": {
|
||||
"name": "IjD171leXnWNBCWoGYrWEO+bbkttsyZiWVIbfkppYfY=",
|
||||
"value": true,
|
||||
"rule_id": "5pMhnqExmNm7AMjLzX4rQw:100.00:7",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1P30kexVjRSR8euiGGrVBuFWjgKtXw1nc9kS+OVOR3I=": {
|
||||
"name": "1P30kexVjRSR8euiGGrVBuFWjgKtXw1nc9kS+OVOR3I=",
|
||||
"value": true,
|
||||
"rule_id": "disabled",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"XmYPKPWXDg+DtsnIqfWpLMAYr9U12wKZlLeTNlYGUpI=": {
|
||||
"name": "XmYPKPWXDg+DtsnIqfWpLMAYr9U12wKZlLeTNlYGUpI=",
|
||||
"value": true,
|
||||
"rule_id": "2JemVomAvNlUrHT8iMSoBi",
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
}
|
||||
},
|
||||
"dynamic_configs": {
|
||||
"PgM6bGhGGwk1AFr2Kt9xS9uSwpoSKhn6JTalMt57glU=": {
|
||||
"name": "PgM6bGhGGwk1AFr2Kt9xS9uSwpoSKhn6JTalMt57glU=",
|
||||
"value": {},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"passed": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"41aKa8VR1PkCOPj1uWqSAMI+qGlQQwJrrvOX0qKat0k=": {
|
||||
"name": "41aKa8VR1PkCOPj1uWqSAMI+qGlQQwJrrvOX0qKat0k=",
|
||||
"value": {
|
||||
"show_avatar_buttons_first": false,
|
||||
"show_econ_buttons_first": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Econ First",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"gl1In+9y7b7T7qCdEngDCdf064wS4IRV7TpvxGjvQlA=": {
|
||||
"name": "gl1In+9y7b7T7qCdEngDCdf064wS4IRV7TpvxGjvQlA=",
|
||||
"value": {
|
||||
"is_rrui": false,
|
||||
"slot_count_non_rrplus": 18,
|
||||
"card_prefab_name": "large"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_rrui",
|
||||
"slot_count_non_rrplus",
|
||||
"card_prefab_name"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uCied9k6g2v6bl+OLLn4Qq4FcpbWL6n4ZllSOXIoi68=": {
|
||||
"name": "uCied9k6g2v6bl+OLLn4Qq4FcpbWL6n4ZllSOXIoi68=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"resolution_check_for_quality_enabled",
|
||||
"resolution_check_for_quality_pixel_count"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GSveKdJ7Eawx+WCF+gWXMIreGMhmXWbuE/K4K/aGV+w=": {
|
||||
"name": "GSveKdJ7Eawx+WCF+gWXMIreGMhmXWbuE/K4K/aGV+w=",
|
||||
"value": {
|
||||
"show_x_to_close": true,
|
||||
"show_ignore_for_now": false,
|
||||
"improve_room_chat_visuals": true,
|
||||
"add_room_chat_emojis": true,
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat": "true",
|
||||
"max_room_chat_preview_lines": 5,
|
||||
"max_room_chat_expanded_lines": 25,
|
||||
"show_select_unselect_all_button": false,
|
||||
"preselect_all_accounts": false,
|
||||
"show_modified_dont_invite_button": true,
|
||||
"show_modified_invite_button": false,
|
||||
"emphasize_reccenter_button": false,
|
||||
"should_autofocus": true,
|
||||
"should_autofocus_search": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "All",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"improve_room_chat_visuals",
|
||||
"add_room_chat_emojis",
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat",
|
||||
"max_room_chat_preview_lines",
|
||||
"max_room_chat_expanded_lines"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+1MoHbg2RSgQWKZFnqbPI8dMIMdxeQEt5K3a120GhC0=": {
|
||||
"name": "+1MoHbg2RSgQWKZFnqbPI8dMIMdxeQEt5K3a120GhC0=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Discovery Sections with Preload",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseDiscoveryServer",
|
||||
"ShouldPreloadDiscoverySections"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tmAb+PrMQJbR9vB1b50NRXS8nPdolHCjGu7cT9nMp8c=": {
|
||||
"name": "tmAb+PrMQJbR9vB1b50NRXS8nPdolHCjGu7cT9nMp8c=",
|
||||
"value": {
|
||||
"save_cached_view_mem": true,
|
||||
"ios_low_max_mirror_mp": 1,
|
||||
"enabled": false,
|
||||
"ios_low_max_mp": 1,
|
||||
"memoryless_aux_surfaces_sharecam": true,
|
||||
"memoryless_aux_surfaces_cachedviews": true,
|
||||
"memoryless_aux_surfaces_pip": false,
|
||||
"memoryless_aux_surfaces_sniper_scope": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "1MP",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ios_low_max_mp"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"a/Cslqd3yO74GHO8C/r3Vgd8+6BVcnyfmbu+7rRwB8Q=": {
|
||||
"name": "a/Cslqd3yO74GHO8C/r3Vgd8+6BVcnyfmbu+7rRwB8Q=",
|
||||
"value": {
|
||||
"pug_max_result_surfacing": 0,
|
||||
"pug_volume_multiplier": 0.75
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Slight Reduction",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"pug_volume_multiplier"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"yUp7cdXqs/0U63mhIWfXWYs1HQkvw92TCyRAi/mcFwA=": {
|
||||
"name": "yUp7cdXqs/0U63mhIWfXWYs1HQkvw92TCyRAi/mcFwA=",
|
||||
"value": {
|
||||
"manipulate_enabled": true,
|
||||
"manipulate_collision_detection_enabled": true,
|
||||
"manipulate_touch_rot_degrees_per_inch": 120,
|
||||
"manipulate_toggle_enabled": true,
|
||||
"manipulate_quick_manipulate_enabled": false,
|
||||
"manipulate_disable_hand_collisions": false,
|
||||
"manipulate_cylindrical_placement_enabled": true,
|
||||
"manipulate_collision_outline_enabled": false,
|
||||
"manipulate_mp_surface_snap": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"manipulate_enabled",
|
||||
"manipulate_collision_detection_enabled",
|
||||
"manipulate_touch_rot_degrees_per_inch"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qE3ySxUYt7KDZ54l5qKzgH9D8syHvJwN1L+B5oLenZ4=": {
|
||||
"name": "qE3ySxUYt7KDZ54l5qKzgH9D8syHvJwN1L+B5oLenZ4=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseServerThumbnailsForGiftDrops"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"IQNESzddmc4JW14PDiryVM3Ch4a3SeVZO2r7mv+55uw=": {
|
||||
"name": "IQNESzddmc4JW14PDiryVM3Ch4a3SeVZO2r7mv+55uw=",
|
||||
"value": {
|
||||
"Enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bv2XeJYBENA04dGGevpDnbE7XG+ylV/Qcu7Jtck93P0=": {
|
||||
"name": "bv2XeJYBENA04dGGevpDnbE7XG+ylV/Qcu7Jtck93P0=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": true,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control ",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"store_item_prefab_variant_name",
|
||||
"hide_rarity",
|
||||
"use_buy_cta_button",
|
||||
"hide_prediscount_price"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ajYfot8/5DpjjWKMm6PPnAWxdENBMxY3bPUkZ5drmQo=": {
|
||||
"name": "ajYfot8/5DpjjWKMm6PPnAWxdENBMxY3bPUkZ5drmQo=",
|
||||
"value": {
|
||||
"react_enable_speedy_radial": true,
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [
|
||||
"TOGGLE_PERSPECTIVE:-1",
|
||||
"GLOBAL_EMOTES:2",
|
||||
"GLOBAL_MIC:0",
|
||||
"GLOBAL_GESTURES:1"
|
||||
],
|
||||
"fade_react_joystick": true,
|
||||
"show_react_joystick": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Fading Joystick Reorder HUD",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"emote_hud_icon_override",
|
||||
"hud_element_priority",
|
||||
"fade_react_joystick",
|
||||
"show_react_joystick"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+YbD06l1RowX9+uSyEc7Nwrr3VKZsi+P8S3psmWn9jk=": {
|
||||
"name": "+YbD06l1RowX9+uSyEc7Nwrr3VKZsi+P8S3psmWn9jk=",
|
||||
"value": {
|
||||
"DefaultUnlockedValueThreshold": 3,
|
||||
"DailyTokenCapOverride": 6217911,
|
||||
"MaximumPostActivityRewardCountOverride": 10,
|
||||
"GiftContextOverride": "GameRewards",
|
||||
"RewardProbabilityOverride": 1,
|
||||
"TokenGiftDrop:Common": 3397,
|
||||
"TokenGiftDrop:Common:Subscribers": 4013,
|
||||
"TokenGiftDrop:Uncommon": 3396,
|
||||
"TokenGiftDrop:Uncommon:Subscribers": 4014,
|
||||
"TokenGiftDrop:Rare": 3395,
|
||||
"TokenGiftDrop:Rare:Subscribers": 4015,
|
||||
"TokenGiftDrop:Epic": 3394,
|
||||
"TokenGiftDrop:Epic:Subscribers": 4016,
|
||||
"IsSearchWithTagsEnabled": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"IsSearchWithTagsEnabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0l4ii2OOIH7tiERyrjyIk7IzE2OmJa2dEvRZhUXgpWM=": {
|
||||
"name": "0l4ii2OOIH7tiERyrjyIk7IzE2OmJa2dEvRZhUXgpWM=",
|
||||
"value": {
|
||||
"can_give": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"can_give"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"0ENBXbOg503dcZze6leU6O7PGYhDlwWSFqDm9YltUQg=": {
|
||||
"name": "0ENBXbOg503dcZze6leU6O7PGYhDlwWSFqDm9YltUQg=",
|
||||
"value": {
|
||||
"place_r2_support_enabled": true,
|
||||
"place_improved_rotation_enabled": true,
|
||||
"place_touch_depth_enabled": true,
|
||||
"place_touch_tutorial_enabled": true,
|
||||
"place_max_tool_distance": 3,
|
||||
"place_improved_large_objects_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"place_improved_rotation_enabled",
|
||||
"place_touch_depth_enabled",
|
||||
"place_touch_tutorial_enabled",
|
||||
"place_max_tool_distance"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"lfjhB7DhFufP1nG7Ts5/rI9N0o1h4ceez5i7+FjGHIM=": {
|
||||
"name": "lfjhB7DhFufP1nG7Ts5/rI9N0o1h4ceez5i7+FjGHIM=",
|
||||
"value": {
|
||||
"CuratedList_AppNav.WidgetPanel.Profile": "Discovery.PageSource.WWPanel.Profile",
|
||||
"CuratedList_WWPanel.Profile": "Discovery.PageSource.WWPanel.Profile"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WWPanel.Profile"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"nJAV+NpxsPI/2oZioiYRwt3RRQaFv3Y9Ec/SH6gz7RU=": {
|
||||
"name": "nJAV+NpxsPI/2oZioiYRwt3RRQaFv3Y9Ec/SH6gz7RU=",
|
||||
"value": {
|
||||
"CuratedList_AppNav.WidgetPanel.Profile": "Discovery.PageSource.WWPanel.Profile",
|
||||
"CuratedList_WWPanel.Profile": "Discovery.PageSource.WWPanel.Profile"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_AppNav.WidgetPanel.Profile",
|
||||
"CuratedList_WWPanel.Profile"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"04WkeczmUc5D4evGKW1B+y0aVNT9+5ocLzmbJU9Tevw=": {
|
||||
"name": "04WkeczmUc5D4evGKW1B+y0aVNT9+5ocLzmbJU9Tevw=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_unified_avatar_item_search",
|
||||
"UGC_search_text_title",
|
||||
"RRO_items_search_title",
|
||||
"use_new_filter_ordering",
|
||||
"CAI_search_includes_coach_items",
|
||||
"extra_purchasable_item_ids_in_default_search"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"pHaFo2WIobc0Git7EET4WspmTrKtXofM5ix5xcZang8=": {
|
||||
"name": "pHaFo2WIobc0Git7EET4WspmTrKtXofM5ix5xcZang8=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Yovpadm2SZdOFUPCzEKn9Y//kWVggHPTNxrsk9WxesE=": {
|
||||
"name": "Yovpadm2SZdOFUPCzEKn9Y//kWVggHPTNxrsk9WxesE=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "3721940192",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"B8iRrP/4HwTrXi1yPDyhZzlDW8IXyXNsCVyiKcVjWhw=": {
|
||||
"name": "B8iRrP/4HwTrXi1yPDyhZzlDW8IXyXNsCVyiKcVjWhw=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_CommunityBoard",
|
||||
"CuratedList_MobileHome",
|
||||
"CuratedList_PlayMenuTabs"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"6hCos3Ss4EJtSQAlr+EfDHDlrKmD3P0VeKR8I4OI7fE=": {
|
||||
"name": "6hCos3Ss4EJtSQAlr+EfDHDlrKmD3P0VeKR8I4OI7fE=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"GroupName",
|
||||
"PlayMenuCarouselOrdering_1",
|
||||
"remove_chips",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"DFNyKR/x7f8p789x3kBvBbZO8YwroOACFYfY9ZnmSSo=": {
|
||||
"name": "DFNyKR/x7f8p789x3kBvBbZO8YwroOACFYfY9ZnmSSo=",
|
||||
"value": {
|
||||
"allow_juniors_quick_chat": false,
|
||||
"include_online_friends": true,
|
||||
"friends_default_tab": true,
|
||||
"default_friends_if_people_empty": false,
|
||||
"show_detailed_player_search_cards": true,
|
||||
"show_empty_search_call_to_action": true,
|
||||
"hide_local_people_search": false,
|
||||
"show_search_players_text_for_remote_search": true,
|
||||
"show_progression_on_watch_home": true,
|
||||
"show_progression_profile": true,
|
||||
"show_progression_notifications": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_empty_search_call_to_action"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qPftwi3e0/765C5wE1Q1PnRKvHBDNehnSTt0LB++x/0=": {
|
||||
"name": "qPftwi3e0/765C5wE1Q1PnRKvHBDNehnSTt0LB++x/0=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": false,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"create_tab_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"a/9cEoc27VlrEygqMwqqACZxnXe1IyzUiHNkGP8bRXw=": {
|
||||
"name": "a/9cEoc27VlrEygqMwqqACZxnXe1IyzUiHNkGP8bRXw=",
|
||||
"value": {
|
||||
"use_new_room_card": true,
|
||||
"use_new_club_event_card": true,
|
||||
"use_new_item_cards": true,
|
||||
"use_new_account_cards": true,
|
||||
"use_vnext_db_room_carousels": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New Club and Event Cards",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_new_club_event_card"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bjJCJqtznpB5nFxkofUS4jB9Rb7WF7w/ZAyugESiaO0=": {
|
||||
"name": "bjJCJqtznpB5nFxkofUS4jB9Rb7WF7w/ZAyugESiaO0=",
|
||||
"value": {
|
||||
"show_x_to_close": true,
|
||||
"show_ignore_for_now": false,
|
||||
"improve_room_chat_visuals": true,
|
||||
"add_room_chat_emojis": true,
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat": "false",
|
||||
"max_room_chat_preview_lines": 5,
|
||||
"max_room_chat_expanded_lines": 21,
|
||||
"show_select_unselect_all_button": false,
|
||||
"preselect_all_accounts": false,
|
||||
"show_modified_dont_invite_button": true,
|
||||
"show_modified_invite_button": false,
|
||||
"emphasize_reccenter_button": false,
|
||||
"should_autofocus": true,
|
||||
"should_autofocus_search": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test 1 - X to Close",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_x_to_close",
|
||||
"show_ignore_for_now"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ee2NBBXErfK/X6nP0a2VdPrscXZ0Gg5oyxajPP9vMj8=": {
|
||||
"name": "ee2NBBXErfK/X6nP0a2VdPrscXZ0Gg5oyxajPP9vMj8=",
|
||||
"value": {
|
||||
"card_size": "large",
|
||||
"hidden_types": [],
|
||||
"display_order": [],
|
||||
"display order": {},
|
||||
"isPromptingLowestTokenPackage": false,
|
||||
"prompt_lowest_token_package": false,
|
||||
"only_prompt_non_payer": true,
|
||||
"hide_token_store": false,
|
||||
"hide_buy_token_and_items": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Prompt Lowest or Token Store",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hide_token_store",
|
||||
"hide_buy_token_and_items"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"7NfKCxK8JfFZj+U5wsa2eXQ9QGf6+Pi0zM6vYbkLe34=": {
|
||||
"name": "7NfKCxK8JfFZj+U5wsa2eXQ9QGf6+Pi0zM6vYbkLe34=",
|
||||
"value": {
|
||||
"save_cached_view_mem": true,
|
||||
"ios_low_max_mirror_mp": 1,
|
||||
"enabled": false,
|
||||
"ios_low_max_mp": 1,
|
||||
"memoryless_aux_surfaces_sharecam": true,
|
||||
"memoryless_aux_surfaces_cachedviews": true,
|
||||
"memoryless_aux_surfaces_pip": false,
|
||||
"memoryless_aux_surfaces_sniper_scope": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"save_cached_view_mem"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ZKfweqgVu014Y1ndfhFrtll5GfUExTMCj3aSs03rerk=": {
|
||||
"name": "ZKfweqgVu014Y1ndfhFrtll5GfUExTMCj3aSs03rerk=",
|
||||
"value": {
|
||||
"lazy_load_objective_log_hud": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"cjwFdrKxttFJuD6KVCjWpFguyiV2ETtrrx36aiHqoME=": {
|
||||
"name": "cjwFdrKxttFJuD6KVCjWpFguyiV2ETtrrx36aiHqoME=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"DataCollection.SkipPerfStatsFor"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BxsBxBQwevMpQr16kmA9WZprwz6R4Zn+r6YA9579e9g=": {
|
||||
"name": "BxsBxBQwevMpQr16kmA9WZprwz6R4Zn+r6YA9579e9g=",
|
||||
"value": {
|
||||
"auto_connect_enabled": false,
|
||||
"recommendations_enabled": false,
|
||||
"drag_and_release_enabled": true,
|
||||
"smart_circuit_enabled": true,
|
||||
"spawn_time_threshold_seconds": 0.5,
|
||||
"spawn_indicator_threshold_seconds": 0.3,
|
||||
"spawn_distance_threshold": 0.005,
|
||||
"dismiss_distance_threshold": 0.5,
|
||||
"max_allowed_dialog_distance": 1,
|
||||
"max_dialog_alignment_distance": 2.5,
|
||||
"max_recommendations_per_port": 50,
|
||||
"ideal_recommendations_no": 25,
|
||||
"backfill_recommendation_results": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Smart Circuits No Recs",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"smart_circuit_enabled",
|
||||
"drag_and_release_enabled",
|
||||
"auto_connect_enabled",
|
||||
"recommendations_enabled",
|
||||
"backfill_recommendation_results"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"OU2TnrYEqCCazsPKl9YYtpZC350bUmnOgOSu1Tk3uT0=": {
|
||||
"name": "OU2TnrYEqCCazsPKl9YYtpZC350bUmnOgOSu1Tk3uT0=",
|
||||
"value": {
|
||||
"optimize_expresso_ui": false,
|
||||
"optimize_raycasts": false,
|
||||
"optimize_nearest_canvas": false,
|
||||
"cache_uri_route_strings": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"optimize_raycasts"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1BKItr6C4GrJ1k7fvnO+v2H1Ff2W0da2YyZIUDDPhkI=": {
|
||||
"name": "1BKItr6C4GrJ1k7fvnO+v2H1Ff2W0da2YyZIUDDPhkI=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": false,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"improved_categorization",
|
||||
"improve_ui",
|
||||
"use_server_thumbnail",
|
||||
"add_token_counter"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"TYT8Skk6AtLGKi7rWzB56x6EaMwRlWHL/9uHS1TGk8k=": {
|
||||
"name": "TYT8Skk6AtLGKi7rWzB56x6EaMwRlWHL/9uHS1TGk8k=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"iAUicne8gLXI/c8rRRwCubhdbfeibRYF88+HxFknLHM=": {
|
||||
"name": "iAUicne8gLXI/c8rRRwCubhdbfeibRYF88+HxFknLHM=",
|
||||
"value": {
|
||||
"IsSearchWithTagsEnabled": true,
|
||||
"UseElasticsearch": true,
|
||||
"UseSecondaryOrdering": true,
|
||||
"UseFuzzySearch": true,
|
||||
"SearchDescription": false,
|
||||
"NameRelevanceBoost": 1,
|
||||
"TagRelevanceBoost": 1,
|
||||
"DescriptionRelevanceBoost": 1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseFuzzySearch",
|
||||
"UseElasticsearch",
|
||||
"NameRelevanceBoost",
|
||||
"TagRelevanceBoost",
|
||||
"DescriptionRelevanceBoost",
|
||||
"SearchDescription"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"t3UuLXc27DdwEHksCz8pyxOpHwvqiN/PnNZqTjfV1Cs=": {
|
||||
"name": "t3UuLXc27DdwEHksCz8pyxOpHwvqiN/PnNZqTjfV1Cs=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Q8udKxjYbVQlxP67MylFm1c7iu2C7fFKhi7JnZj9gdg=": {
|
||||
"name": "Q8udKxjYbVQlxP67MylFm1c7iu2C7fFKhi7JnZj9gdg=",
|
||||
"value": {
|
||||
"online_ranking_configuration_name": "2025q2_personalized_all_items",
|
||||
"use_online_ranking": true,
|
||||
"number_of_entities_to_shuffle": 18,
|
||||
"use_contextual_features": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"online_ranking_configuration_name",
|
||||
"use_online_ranking",
|
||||
"number_of_entities_to_shuffle",
|
||||
"use_contextual_features"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"m5vegogGsVm25NjoYueUUVFyYkpCjS9GXmOJyoTPoA0=": {
|
||||
"name": "m5vegogGsVm25NjoYueUUVFyYkpCjS9GXmOJyoTPoA0=",
|
||||
"value": {
|
||||
"enable_retain_ui_state": true,
|
||||
"show_watch_button_in_menus": false,
|
||||
"portal_buttons_act_like_tabs": false,
|
||||
"portal_always_present": false,
|
||||
"enable_retain_cursor_position": true,
|
||||
"retain_ui_state_duration": 60,
|
||||
"use_dir_nav": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Not showing Permanent portal",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"retain_ui_state_duration",
|
||||
"portal_always_present"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"KzCwjNQZAg0cBqAmTvVVIhDjFBRvS/PHveadm06gSQY=": {
|
||||
"name": "KzCwjNQZAg0cBqAmTvVVIhDjFBRvS/PHveadm06gSQY=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_roomie_in_hud"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+QqFqQwgBCfW2lwXJBJiY56/bpBDNpBnWkOiVX0UXVg=": {
|
||||
"name": "+QqFqQwgBCfW2lwXJBJiY56/bpBDNpBnWkOiVX0UXVg=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tryon",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": false,
|
||||
"ShowTokenStore": false,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": true,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ButtonDisplayOrder",
|
||||
"ShowRRPlusButton",
|
||||
"ShowRecommendedForYouButton",
|
||||
"ShowTokenStore",
|
||||
"ShowCustomClothingButton",
|
||||
"ShowDormMirrorPurchaseCampaignButton",
|
||||
"DormMirrorPurchaseCampaignID",
|
||||
"ShowStoreBadgeNotification",
|
||||
"show_shopping_bag_on_add",
|
||||
"ShowTryOnButton",
|
||||
"TryOnButtonText",
|
||||
"TryOnImageIndex"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JmhBvrsUSNdeTAMKIFXS+2lbUH6cO358HJgdLhO29Ow=": {
|
||||
"name": "JmhBvrsUSNdeTAMKIFXS+2lbUH6cO358HJgdLhO29Ow=",
|
||||
"value": {
|
||||
"use_item_rrp_upsell": true,
|
||||
"show_for_save": false,
|
||||
"show_for_publish": false,
|
||||
"RecRoomPlusSignUpBonusIds": [
|
||||
3
|
||||
]
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "6000 - 10000 price range",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"RecRoomPlusSignUpBonusIds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"TaRiAV7AbWl0yGMVetfN+4io5KDM4VOlksn29djzH4A=": {
|
||||
"name": "TaRiAV7AbWl0yGMVetfN+4io5KDM4VOlksn29djzH4A=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"SqkpBToF+veu3mfZXWtz2k9g4WMYLzUIttdJTIOhRaw=": {
|
||||
"name": "SqkpBToF+veu3mfZXWtz2k9g4WMYLzUIttdJTIOhRaw=",
|
||||
"value": {
|
||||
"is_daily_challenge_variations_enabled": false,
|
||||
"daily_objectives_override": {},
|
||||
"is_daily_challenge_discoverability_enabled": false,
|
||||
"is_new_ui": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_daily_challenge_discoverability_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"/tRggJqRp3TMUDOdfQizB3MxD06L1KuKkQgiy7jm0Gg=": {
|
||||
"name": "/tRggJqRp3TMUDOdfQizB3MxD06L1KuKkQgiy7jm0Gg=",
|
||||
"value": {
|
||||
"display_intro": true,
|
||||
"GroupName": "Test"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Q9WbsqyRj5FCx89YOWFGgXAWNZRGHVErLug7upf9Qps=": {
|
||||
"name": "Q9WbsqyRj5FCx89YOWFGgXAWNZRGHVErLug7upf9Qps=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Z/6AiUyNbXuqn76Qu7+bZWPyPEBvoGH87yRH6euEqGw=": {
|
||||
"name": "Z/6AiUyNbXuqn76Qu7+bZWPyPEBvoGH87yRH6euEqGw=",
|
||||
"value": {
|
||||
"rrplus_paywall_type": "prevent_confirming_creation",
|
||||
"number_of_saved_outfits": 100,
|
||||
"show_mirror_button": true,
|
||||
"show_top_level_tab": true,
|
||||
"show_customizer_card": true,
|
||||
"show_back_facing_custom_shirt_preview": false,
|
||||
"show_new_customizer_buttons": true,
|
||||
"show_undo_redo_buttons": true,
|
||||
"default_custom_shirt_price": 1000
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "All Buttons",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_new_customizer_buttons",
|
||||
"show_undo_redo_buttons"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"NnLolbVqJjOx1IcCrK0c8MlL1TllX6st/f0R9ndXPfE=": {
|
||||
"name": "NnLolbVqJjOx1IcCrK0c8MlL1TllX6st/f0R9ndXPfE=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"vyzYvPPEq/Uklyspw/Clqkk++tk+s7T+pujz/hNy11k=": {
|
||||
"name": "vyzYvPPEq/Uklyspw/Clqkk++tk+s7T+pujz/hNy11k=",
|
||||
"value": {
|
||||
"canSeeBadgeNotifications": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9DFkM0x7+Xebz5JnvYXwuvzr3pvCgSB5bB976TCpTnY=": {
|
||||
"name": "9DFkM0x7+Xebz5JnvYXwuvzr3pvCgSB5bB976TCpTnY=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"iosHighFPS"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"U+C0JE0lCiKwA6qbEdn5zi84kcQZQxGNgCnLyBO+Mvg=": {
|
||||
"name": "U+C0JE0lCiKwA6qbEdn5zi84kcQZQxGNgCnLyBO+Mvg=",
|
||||
"value": {
|
||||
"isEnabled": false,
|
||||
"useAlternateIcon": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"CIoQrHWXgnp8Q+h2+B794ebe1HlCMKk+BE9E7GhC76U=": {
|
||||
"name": "CIoQrHWXgnp8Q+h2+B794ebe1HlCMKk+BE9E7GhC76U=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"full_body_avatars_enabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2185387223",
|
||||
"gateValue": "true",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"zc3+m+yYTKA4djvy6mr018HLedAonpTT/FlFQxRtU24=": {
|
||||
"name": "zc3+m+yYTKA4djvy6mr018HLedAonpTT/FlFQxRtU24=",
|
||||
"value": {
|
||||
"is_enabled": false,
|
||||
"notice_on_visit_probability": 0,
|
||||
"notice_duration_seconds": 5,
|
||||
"message_string": "Voice moderation is active in this room.",
|
||||
"title_string": "Coach is listening!",
|
||||
"moderated_player_only": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_enabled",
|
||||
"notice_on_visit_probability",
|
||||
"notice_duration_seconds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"sFCosCySw1yjbNhB/m+DeVNaemyYpX6qW+vPJC00djs=": {
|
||||
"name": "sFCosCySw1yjbNhB/m+DeVNaemyYpX6qW+vPJC00djs=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"full_body_avatars_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"KO0An6vCwPs5qfJaEZMizrZeqks0IvKqcdAGcIOzj8I=": {
|
||||
"name": "KO0An6vCwPs5qfJaEZMizrZeqks0IvKqcdAGcIOzj8I=",
|
||||
"value": {
|
||||
"show_x_to_close": true,
|
||||
"show_ignore_for_now": false,
|
||||
"improve_room_chat_visuals": true,
|
||||
"add_room_chat_emojis": true,
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat": "false",
|
||||
"max_room_chat_preview_lines": 5,
|
||||
"max_room_chat_expanded_lines": 21,
|
||||
"show_select_unselect_all_button": false,
|
||||
"preselect_all_accounts": false,
|
||||
"show_modified_dont_invite_button": true,
|
||||
"show_modified_invite_button": false,
|
||||
"emphasize_reccenter_button": false,
|
||||
"should_autofocus": true,
|
||||
"should_autofocus_search": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"emphasize_reccenter_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ZGvuoZWFepX6OTZc00Uf47ZUreyJwIGKcO/TJ0/oLH4=": {
|
||||
"name": "ZGvuoZWFepX6OTZc00Uf47ZUreyJwIGKcO/TJ0/oLH4=",
|
||||
"value": {
|
||||
"show_x_to_close": true,
|
||||
"show_ignore_for_now": false,
|
||||
"improve_room_chat_visuals": true,
|
||||
"add_room_chat_emojis": true,
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat": "false",
|
||||
"max_room_chat_preview_lines": 5,
|
||||
"max_room_chat_expanded_lines": 21,
|
||||
"show_select_unselect_all_button": false,
|
||||
"preselect_all_accounts": false,
|
||||
"show_modified_dont_invite_button": true,
|
||||
"show_modified_invite_button": false,
|
||||
"emphasize_reccenter_button": false,
|
||||
"should_autofocus": true,
|
||||
"should_autofocus_search": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"should_autofocus"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9KEMvzms33QH4N4d7nlDhD5vI05R7ZQTZEnRG5mhafs=": {
|
||||
"name": "9KEMvzms33QH4N4d7nlDhD5vI05R7ZQTZEnRG5mhafs=",
|
||||
"value": {
|
||||
"third_person_button_variant": "LARGE_LIVE_TQ",
|
||||
"third_person_button_icon": "EYE",
|
||||
"third_person_button_layout": "LARGE_RADIAL"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"third_person_button_variant"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hye9UIQca/dKwIwO34ij4tOxRQl9W8NobhMs0IrLZGI=": {
|
||||
"name": "hye9UIQca/dKwIwO34ij4tOxRQl9W8NobhMs0IrLZGI=",
|
||||
"value": {
|
||||
"removePortraitMode": false,
|
||||
"recnet_deserialize_json_on_background_thread_length_threshold": 1000,
|
||||
"recnet_use_besthttp_request_queue": false,
|
||||
"parallelize_photon_connection": true,
|
||||
"start_critical_operations_early": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"parallelize_photon_connection"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "795959371",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7zLAnzZ4AiAvCAgX6xB3ZL:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"+P4XiX6CdKIjBTJSK9SyNrnkvFYqByuB5oU4nVWthy4=": {
|
||||
"name": "+P4XiX6CdKIjBTJSK9SyNrnkvFYqByuB5oU4nVWthy4=",
|
||||
"value": {
|
||||
"canned_chat_RecRoomDefault": [
|
||||
"Hi!",
|
||||
"Bye!",
|
||||
"Ready?",
|
||||
"BRB",
|
||||
"Let’s go!"
|
||||
],
|
||||
"canned_chat_RoomDefault": [
|
||||
"Come with me",
|
||||
"Help!",
|
||||
"Let's party up!",
|
||||
"Let's be friends!",
|
||||
"May I pick the room?"
|
||||
],
|
||||
"canned_chat_CustomA": [
|
||||
"Yes",
|
||||
"OK",
|
||||
"Thanks",
|
||||
"LOL",
|
||||
"You look great!"
|
||||
],
|
||||
"canned_chat_CustomB": [
|
||||
"No",
|
||||
"Boo",
|
||||
"Sorry",
|
||||
"I don’t like this",
|
||||
"What the coach?!"
|
||||
],
|
||||
"room_chat_bubble_use_revamped_ui": true,
|
||||
"room_chat_bubble_font_size_mult": 1.5,
|
||||
"room_chat_bubble_bg_color": "#ffffffaa",
|
||||
"room_chat_bubble_text_color": "black",
|
||||
"room_chat_bubble_max_emoji_embiggen_len": 1,
|
||||
"room_chat_bubble_embiggen_mult": 2,
|
||||
"room_chat_bubble_should_embolden": true,
|
||||
"room_chat_bubble_text_alpha": [
|
||||
1,
|
||||
0.8,
|
||||
0.6
|
||||
],
|
||||
"room_chat_bubble_bg_alpha": [
|
||||
0.66,
|
||||
0.5,
|
||||
0.3
|
||||
],
|
||||
"room_chat_canned_detached_mode": "right",
|
||||
"show_party_chat_in_room_chat_menu": true,
|
||||
"enable_party_voice_toggle_in_room_chat": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Simplified",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"canned_chat_RecRoomDefault",
|
||||
"canned_chat_RoomDefault",
|
||||
"canned_chat_CustomA",
|
||||
"canned_chat_CustomB"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fqcoOZYHiDvsidKSPwhob3wuAaZwakjdLZHmlxE9cEE=": {
|
||||
"name": "fqcoOZYHiDvsidKSPwhob3wuAaZwakjdLZHmlxE9cEE=",
|
||||
"value": {
|
||||
"clusterlod_shadow_experiment_enum": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "LowQualityShadow",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"rBRO//FC+zvTP46dWLUVs9IHxkr538Y92BKAinb5jto=": {
|
||||
"name": "rBRO//FC+zvTP46dWLUVs9IHxkr538Y92BKAinb5jto=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"O5/tO6LrQtiCC3SN4e61LRVZlvcOW0XheIwJiW4c3Fc=": {
|
||||
"name": "O5/tO6LrQtiCC3SN4e61LRVZlvcOW0XheIwJiW4c3Fc=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": true,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": false,
|
||||
"options_count_per_platform": {},
|
||||
"AutoCompleteIndexName": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_autocomplete",
|
||||
"options_count_per_platform",
|
||||
"AutoCompleteIndexName"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"vGyLakl1pKjNaLI9qwe9MiiQ6hE8trz8SM24J8EHeE4=": {
|
||||
"name": "vGyLakl1pKjNaLI9qwe9MiiQ6hE8trz8SM24J8EHeE4=",
|
||||
"value": {
|
||||
"show_reactions": true,
|
||||
"show_emotes": true,
|
||||
"reactions": [
|
||||
"👍",
|
||||
"❤️",
|
||||
"🎉",
|
||||
"😭:Sad",
|
||||
"👎",
|
||||
"😲:Shocked",
|
||||
"💀",
|
||||
"🤣:Laugh"
|
||||
],
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [],
|
||||
"show_react_gesture_tip": false,
|
||||
"fade_react_joystick": false,
|
||||
"show_react_joystick": false,
|
||||
"react_gesture_tip": "",
|
||||
"show_react_radial_joystick": false,
|
||||
"custom_local_profile": false,
|
||||
"show_profile_blocklist": false,
|
||||
"hide_old_set_badges": false,
|
||||
"Tip:Visibility": 0,
|
||||
"snowball_enabled": false,
|
||||
"snowball_feedback_sizing": false,
|
||||
"snowball_feedback_vfx": false,
|
||||
"snowball_feedback_room_chat": false,
|
||||
"snowball_feedback_notification": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "8 Reactions Plus Emotes",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_reactions",
|
||||
"show_emotes",
|
||||
"reactions"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9vAT9l7+TJd+sv7cRKJCRY2GP/ptWoKCAH57ZV7Wj90=": {
|
||||
"name": "9vAT9l7+TJd+sv7cRKJCRY2GP/ptWoKCAH57ZV7Wj90=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Make Your Own",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"copy_room_button_text"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"SCczfd8qsppn7dkyUZkERoCmcX2M2AgCOZcw9Chm0Lg=": {
|
||||
"name": "SCczfd8qsppn7dkyUZkERoCmcX2M2AgCOZcw9Chm0Lg=",
|
||||
"value": {
|
||||
"DefaultUnlockedValueThreshold": 3,
|
||||
"DailyTokenCapOverride": 6217911,
|
||||
"MaximumPostActivityRewardCountOverride": 10,
|
||||
"GiftContextOverride": "GameRewards",
|
||||
"RewardProbabilityOverride": 1,
|
||||
"TokenGiftDrop:Common": 3397,
|
||||
"TokenGiftDrop:Common:Subscribers": 4013,
|
||||
"TokenGiftDrop:Uncommon": 3396,
|
||||
"TokenGiftDrop:Uncommon:Subscribers": 4014,
|
||||
"TokenGiftDrop:Rare": 3395,
|
||||
"TokenGiftDrop:Rare:Subscribers": 4015,
|
||||
"TokenGiftDrop:Epic": 3394,
|
||||
"TokenGiftDrop:Epic:Subscribers": 4016,
|
||||
"IsSearchWithTagsEnabled": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"DefaultUnlockedValueThreshold"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"LGMiOOn8elDxKIWJ3IPXsqykBXmBkUbCCu7jx2bg5/Y=": {
|
||||
"name": "LGMiOOn8elDxKIWJ3IPXsqykBXmBkUbCCu7jx2bg5/Y=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreInventions"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tbrIhGKeg2COEQ6j9zhoY5epOtAc6hNZuvNfQvqhw94=": {
|
||||
"name": "tbrIhGKeg2COEQ6j9zhoY5epOtAc6hNZuvNfQvqhw94=",
|
||||
"value": {
|
||||
"IsSearchWithTagsEnabled": true,
|
||||
"UseElasticsearch": true,
|
||||
"UseSecondaryOrdering": true,
|
||||
"UseFuzzySearch": true,
|
||||
"SearchDescription": false,
|
||||
"NameRelevanceBoost": 1,
|
||||
"TagRelevanceBoost": 1,
|
||||
"DescriptionRelevanceBoost": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Use fuzzy search",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseFuzzySearch",
|
||||
"UseElasticsearch"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Dv9pGJ5sqmqnRbH9tb8lL8sLi71hT1ygLVqahcFvz1A=": {
|
||||
"name": "Dv9pGJ5sqmqnRbH9tb8lL8sLi71hT1ygLVqahcFvz1A=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tguJn3wxgu7sxsi4XCnasav3MzyBjXiYRNbqjiWvLPU=": {
|
||||
"name": "tguJn3wxgu7sxsi4XCnasav3MzyBjXiYRNbqjiWvLPU=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"lnKTH3l6NnHOep1deMW7ih/tATamiL8zmqY72EDwId0=": {
|
||||
"name": "lnKTH3l6NnHOep1deMW7ih/tATamiL8zmqY72EDwId0=",
|
||||
"value": {
|
||||
"CuratedList_PlayCategories": "Discovery.PageSource.PlayCategories"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ugI19SH6EM5JHWnZUvkNPnP77FxFHvB44MDmduPOD9c=": {
|
||||
"name": "ugI19SH6EM5JHWnZUvkNPnP77FxFHvB44MDmduPOD9c=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tryon",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": false,
|
||||
"ShowTokenStore": false,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": true,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"DormMirrorPurchaseCampaignID"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Q2WOtgg1jF6V5TdrXmYhoRmBreexUu3BnBPEq5/neAU=": {
|
||||
"name": "Q2WOtgg1jF6V5TdrXmYhoRmBreexUu3BnBPEq5/neAU=",
|
||||
"value": {
|
||||
"IsUserAllowlistedForAvatarStudio": false,
|
||||
"AdditionalOutfitTypeEnumNamesPermitted": [],
|
||||
"CanCreateRoomieEquipment": false,
|
||||
"OutfitTypeEnumNamesPermitted": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"IsUserAllowlistedForAvatarStudio",
|
||||
"AdditionalOutfitTypeEnumNamesPermitted",
|
||||
"CanCreateRoomieEquipment",
|
||||
"OutfitTypeEnumNamesPermitted"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"2sLY/mhSkXK4ATFsxVPaPboEL1zmbeidRCJrB6YDLVk=": {
|
||||
"name": "2sLY/mhSkXK4ATFsxVPaPboEL1zmbeidRCJrB6YDLVk=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_watch_architecture_enabled",
|
||||
"browser_tabs_preserve_history_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"kgH/QHAT3O39+Dpzi5eKVluSUmzLV4z6WhwJuXSpFjU=": {
|
||||
"name": "kgH/QHAT3O39+Dpzi5eKVluSUmzLV4z6WhwJuXSpFjU=",
|
||||
"value": {
|
||||
"LayoutType": 0,
|
||||
"UseExtProgRoomDetails": "0",
|
||||
"LayoutKey": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "No External Progression",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3867253699",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "110550847",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"aR8EitJRNObPzDrxdZ17rg5zlkYXnHCBty8SHcpHGWg=": {
|
||||
"name": "aR8EitJRNObPzDrxdZ17rg5zlkYXnHCBty8SHcpHGWg=",
|
||||
"value": {
|
||||
"disable_newplayerchecklist": true,
|
||||
"show_progression_on_watch_home": true,
|
||||
"show_progression_profile": true,
|
||||
"show_progression_notifications": true,
|
||||
"show_progression_loading": true,
|
||||
"Tip:Visibility": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "All",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_progression_on_watch_home",
|
||||
"show_progression_profile",
|
||||
"show_progression_notifications",
|
||||
"show_progression_loading",
|
||||
"Tip:Visibility"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"inF9QExmgMtdMx+ETSpvzWVhW6Jlyd6fpyGzczGYWFo=": {
|
||||
"name": "inF9QExmgMtdMx+ETSpvzWVhW6Jlyd6fpyGzczGYWFo=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Text Only",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"WishlistButtonVariant"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"m3XOJM5PC+Tv9VyeMSlLPj39Cm/jo/Tww15+Wa3RKz8=": {
|
||||
"name": "m3XOJM5PC+Tv9VyeMSlLPj39Cm/jo/Tww15+Wa3RKz8=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"EnableDownloadableAvatarItems"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"5YdgCqgqcjeGn9f9+yZyHrKZm9Bfj5MnSiNGIF7h1bU=": {
|
||||
"name": "5YdgCqgqcjeGn9f9+yZyHrKZm9Bfj5MnSiNGIF7h1bU=",
|
||||
"value": {
|
||||
"can_gift_custom_shirts": true,
|
||||
"can_write_gift_message": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Can Gift Shirts - Cannot Write Message",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"can_gift_custom_shirts",
|
||||
"can_write_gift_message"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BkzetgmaElQgK8PhcFFfMFSLRFXtMcOPJvrVebqr0Qs=": {
|
||||
"name": "BkzetgmaElQgK8PhcFFfMFSLRFXtMcOPJvrVebqr0Qs=",
|
||||
"value": {
|
||||
"remove_dance": false,
|
||||
"remove_reach": false,
|
||||
"remove_emote": false,
|
||||
"remove_emotes_from_room_chat": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"remove_emote",
|
||||
"remove_emotes_from_room_chat"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Pbab4bT16i8DL0ivvWU/IfpKzDemn5anopABczcqRnE=": {
|
||||
"name": "Pbab4bT16i8DL0ivvWU/IfpKzDemn5anopABczcqRnE=",
|
||||
"value": {
|
||||
"isEnabled": true,
|
||||
"buckets": {
|
||||
"128": 64,
|
||||
"256": 128,
|
||||
"512": 256,
|
||||
"1024": 512,
|
||||
"2048": 1024
|
||||
}
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Higher Resolution Large Images",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xqT86xjYKueBzxxD8hOVS0N4BCdRfJVDuPVkP7ukYx4=": {
|
||||
"name": "xqT86xjYKueBzxxD8hOVS0N4BCdRfJVDuPVkP7ukYx4=",
|
||||
"value": {
|
||||
"third_person_feature_set": "FULL",
|
||||
"persistence": true,
|
||||
"Tip:Visibility": 0,
|
||||
"group_dance_with_gestures": true,
|
||||
"reduce_dance_options": false,
|
||||
"default_to_third_person": false,
|
||||
"toggle_in_orientation": true,
|
||||
"pinch_to_zoom": true,
|
||||
"tutorial_in_orientation": true,
|
||||
"new_melee": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Tip:Visibility"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"UtENLKZUduRgrx4FkNsyIDigXAZ+sfuTjrZE8zhKKqs=": {
|
||||
"name": "UtENLKZUduRgrx4FkNsyIDigXAZ+sfuTjrZE8zhKKqs=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": false,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": true,
|
||||
"options_count_per_platform": {
|
||||
"mobile": 4,
|
||||
"pc": 4,
|
||||
"vr": 4
|
||||
},
|
||||
"AutoCompleteIndexName": "room-autocomplete_inc_popular_rooms"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hide_filters",
|
||||
"show_history_with_chips",
|
||||
"show_history_with_button",
|
||||
"show_history"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"8cU8B1Ekvu420q24+XAy/461HHmGQN7XamY6pQwMy04=": {
|
||||
"name": "8cU8B1Ekvu420q24+XAy/461HHmGQN7XamY6pQwMy04=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": false,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"NoNavBar"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"X93a6IXPXXkv+KSeK7LNYFMwQ8sx/2poliFbuwMNCKw=": {
|
||||
"name": "X93a6IXPXXkv+KSeK7LNYFMwQ8sx/2poliFbuwMNCKw=",
|
||||
"value": {
|
||||
"canned_chat_RecRoomDefault": [
|
||||
"Hi!",
|
||||
"Bye!",
|
||||
"Ready?",
|
||||
"BRB",
|
||||
"Let’s go!"
|
||||
],
|
||||
"canned_chat_RoomDefault": [
|
||||
"Come with me",
|
||||
"Help!",
|
||||
"Let's party up!",
|
||||
"Let's be friends!",
|
||||
"May I pick the room?"
|
||||
],
|
||||
"canned_chat_CustomA": [
|
||||
"Yes",
|
||||
"OK",
|
||||
"Thanks",
|
||||
"LOL",
|
||||
"You look great!"
|
||||
],
|
||||
"canned_chat_CustomB": [
|
||||
"No",
|
||||
"Boo",
|
||||
"Sorry",
|
||||
"I don’t like this",
|
||||
"What the coach?!"
|
||||
],
|
||||
"room_chat_bubble_use_revamped_ui": true,
|
||||
"room_chat_bubble_font_size_mult": 1.5,
|
||||
"room_chat_bubble_bg_color": "#ffffffaa",
|
||||
"room_chat_bubble_text_color": "black",
|
||||
"room_chat_bubble_max_emoji_embiggen_len": 1,
|
||||
"room_chat_bubble_embiggen_mult": 2,
|
||||
"room_chat_bubble_should_embolden": true,
|
||||
"room_chat_bubble_text_alpha": [
|
||||
1,
|
||||
0.8,
|
||||
0.6
|
||||
],
|
||||
"room_chat_bubble_bg_alpha": [
|
||||
0.66,
|
||||
0.5,
|
||||
0.3
|
||||
],
|
||||
"room_chat_canned_detached_mode": "right",
|
||||
"show_party_chat_in_room_chat_menu": true,
|
||||
"enable_party_voice_toggle_in_room_chat": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Detached Right",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"room_chat_canned_detached_mode"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zkycOSwLIVx19ABnVHYbXcwIr7Er3MExLJxxnBe4eZQ=": {
|
||||
"name": "zkycOSwLIVx19ABnVHYbXcwIr7Er3MExLJxxnBe4eZQ=",
|
||||
"value": {
|
||||
"Enabled": false,
|
||||
"Scale": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JaLfvirDFK2e2rKtjZcHMwLQw3Ieu9Vcz4bqlEJqPl0=": {
|
||||
"name": "JaLfvirDFK2e2rKtjZcHMwLQw3Ieu9Vcz4bqlEJqPl0=",
|
||||
"value": {
|
||||
"canned_chat_RecRoomDefault": [
|
||||
"Hi!",
|
||||
"Bye!",
|
||||
"Ready?",
|
||||
"BRB",
|
||||
"Let’s go!"
|
||||
],
|
||||
"canned_chat_RoomDefault": [
|
||||
"Come with me",
|
||||
"Help!",
|
||||
"Let's party up!",
|
||||
"Let's be friends!",
|
||||
"May I pick the room?"
|
||||
],
|
||||
"canned_chat_CustomA": [
|
||||
"Yes",
|
||||
"OK",
|
||||
"Thanks",
|
||||
"LOL",
|
||||
"You look great!"
|
||||
],
|
||||
"canned_chat_CustomB": [
|
||||
"No",
|
||||
"Boo",
|
||||
"Sorry",
|
||||
"I don’t like this",
|
||||
"What the coach?!"
|
||||
],
|
||||
"room_chat_bubble_use_revamped_ui": true,
|
||||
"room_chat_bubble_font_size_mult": 1.5,
|
||||
"room_chat_bubble_bg_color": "#ffffffaa",
|
||||
"room_chat_bubble_text_color": "black",
|
||||
"room_chat_bubble_max_emoji_embiggen_len": 1,
|
||||
"room_chat_bubble_embiggen_mult": 2,
|
||||
"room_chat_bubble_should_embolden": true,
|
||||
"room_chat_bubble_text_alpha": [
|
||||
1,
|
||||
0.8,
|
||||
0.6
|
||||
],
|
||||
"room_chat_bubble_bg_alpha": [
|
||||
0.66,
|
||||
0.5,
|
||||
0.3
|
||||
],
|
||||
"room_chat_canned_detached_mode": "right",
|
||||
"show_party_chat_in_room_chat_menu": true,
|
||||
"enable_party_voice_toggle_in_room_chat": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_party_chat_in_room_chat_menu"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"HIti2YBj+J4msMJwvuzeQz1iaTidGifRFqrI9B0SgFI=": {
|
||||
"name": "HIti2YBj+J4msMJwvuzeQz1iaTidGifRFqrI9B0SgFI=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": false,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"has_more_customization_access_points"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9KDM4jpn354tBGeL0HbWz3dFM6gzoX6LwESg7nrgEAs=": {
|
||||
"name": "9KDM4jpn354tBGeL0HbWz3dFM6gzoX6LwESg7nrgEAs=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"E0OtDIdP58hCsWlq7zDILWXI8yuty9Vf9JnD0TWKDDk=": {
|
||||
"name": "E0OtDIdP58hCsWlq7zDILWXI8yuty9Vf9JnD0TWKDDk=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test Switching Option",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseModernFloatingBeanAvatar",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"aGtwhBXAumu+2amx29UTKM60CfrZzBA4UGedsqg0Dt4=": {
|
||||
"name": "aGtwhBXAumu+2amx29UTKM60CfrZzBA4UGedsqg0Dt4=",
|
||||
"value": {
|
||||
"IsNewAccountDefaultMuted": false,
|
||||
"RedIconWhenMuted": false,
|
||||
"RedIconWhenSpeakingAndMuted": false,
|
||||
"HighlightWhenMutedAndOtherPlayersTalking": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"IsNewAccountDefaultMuted",
|
||||
"RedIconWhenMuted",
|
||||
"RedIconWhenSpeakingAndMuted",
|
||||
"HighlightWhenMutedAndOtherPlayersTalking"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Zo3mUdqxwFDs/6OIhpGE2zrhSLBsJumvx1mADgShIBc=": {
|
||||
"name": "Zo3mUdqxwFDs/6OIhpGE2zrhSLBsJumvx1mADgShIBc=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "FBA as default and Tops page default",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"avatar_body_type_default",
|
||||
"default_nux_customize_tab_name"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"MGDsAG+WvTsDPDhfJvwANRtfklX3srvXl/5KNnc9dZU=": {
|
||||
"name": "MGDsAG+WvTsDPDhfJvwANRtfklX3srvXl/5KNnc9dZU=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"add_token_counter"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Z8IjYo1R7AISPwYVXWRU3QSXwrCSQLcYoqJwC+3xDAA=": {
|
||||
"name": "Z8IjYo1R7AISPwYVXWRU3QSXwrCSQLcYoqJwC+3xDAA=",
|
||||
"value": {
|
||||
"react_enable_speedy_radial": true,
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [
|
||||
"TOGGLE_PERSPECTIVE:-1",
|
||||
"GLOBAL_EMOTES:2",
|
||||
"GLOBAL_MIC:0",
|
||||
"GLOBAL_GESTURES:1"
|
||||
],
|
||||
"fade_react_joystick": true,
|
||||
"show_react_joystick": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"react_enable_speedy_radial"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QJeyZguCYUDw9zI5fCnAFSDK1nx74C84rAbfInpDPvE=": {
|
||||
"name": "QJeyZguCYUDw9zI5fCnAFSDK1nx74C84rAbfInpDPvE=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_unified_avatar_item_search",
|
||||
"UGC_search_text_title",
|
||||
"RRO_items_search_title",
|
||||
"use_new_filter_ordering",
|
||||
"look_up_gift_drop_from_local_cache",
|
||||
"CAI_search_includes_coach_items",
|
||||
"new_outfit_page_logic",
|
||||
"cai_default_ordering"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"sUwQGWqUacb6a67shgflNI0SB1wppzp9Dt5EzxQ8Is0=": {
|
||||
"name": "sUwQGWqUacb6a67shgflNI0SB1wppzp9Dt5EzxQ8Is0=",
|
||||
"value": {
|
||||
"Retention.Coordinate.PartyBeaconEnabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Retention.Coordinate.PartyBeaconEnabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uGCc+kIf6Y6AhId/rRFHivIzWYDfmUJwPFRIS2frPpE=": {
|
||||
"name": "uGCc+kIf6Y6AhId/rRFHivIzWYDfmUJwPFRIS2frPpE=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"cai_default_ordering"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Fk0TgDmp0yHW6creMK7Eb2oXkOYSk7kTdcgnU55jYH8=": {
|
||||
"name": "Fk0TgDmp0yHW6creMK7Eb2oXkOYSk7kTdcgnU55jYH8=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1,
|
||||
"Roomie.Pruner.BatchSize": "50",
|
||||
"Roomie.Pruner.MaxConversationSize": "150"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Roomie.Pruner.BatchSize",
|
||||
"Roomie.Pruner.MaxConversationSize"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qqAhvWP2zBJ8n+JlzF8BI+KGYcERat5Il3bgI6UbQ3E=": {
|
||||
"name": "qqAhvWP2zBJ8n+JlzF8BI+KGYcERat5Il3bgI6UbQ3E=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GDipeM1fo8FDRRSMtleBqTdTLUolim/qFUIGOgGkBlU=": {
|
||||
"name": "GDipeM1fo8FDRRSMtleBqTdTLUolim/qFUIGOgGkBlU=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_MobileHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9fwZePPhj906JP4YKtsSZ07SrTsPXWN2B59LjJfNYRk=": {
|
||||
"name": "9fwZePPhj906JP4YKtsSZ07SrTsPXWN2B59LjJfNYRk=",
|
||||
"value": {
|
||||
"hide_custom_tab": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ll+VDSpWKRfjer61FAzB586d6v3FOPjZBUNrQuRvoZQ=": {
|
||||
"name": "Ll+VDSpWKRfjer61FAzB586d6v3FOPjZBUNrQuRvoZQ=",
|
||||
"value": {
|
||||
"show_rrui_for_leave_party": true,
|
||||
"show_room_capacity_party_size_warning": false,
|
||||
"show_detailed_account_cards": true,
|
||||
"show_separate_partychat_button": true,
|
||||
"show_party_buttons_on_top": true,
|
||||
"show_goto_in_partychat_details": true,
|
||||
"use_party_invite_message": false,
|
||||
"show_party_chat_in_room_chat_menu": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_party_invite_message"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"YTGf47KSC7UnzcrMO9DCUn0EI9MA8El8KCou8+L46EY=": {
|
||||
"name": "YTGf47KSC7UnzcrMO9DCUn0EI9MA8El8KCou8+L46EY=",
|
||||
"value": {
|
||||
"algo_name": "InventionsHotScoreTrending",
|
||||
"algo_version": "5",
|
||||
"show_curated_list": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_curated_list"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"iBz+BUnPg8ibZ69mJii1TW8yY+ysgZ0E4XvrEl/DXgw=": {
|
||||
"name": "iBz+BUnPg8ibZ69mJii1TW8yY+ysgZ0E4XvrEl/DXgw=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": true,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": true,
|
||||
"options_count_per_platform": {
|
||||
"mobile": 4,
|
||||
"pc": 4,
|
||||
"vr": 4
|
||||
},
|
||||
"AutoCompleteIndexName": "room-autocomplete_inc_popular_rooms"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Search Tips",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_search_tips",
|
||||
"show_similar_rooms_carousel",
|
||||
"show_custom_carousel",
|
||||
"use_legacy_null_search"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"RuOJsq/00lpPpPmx0pLVraCo//x44e7Nv4ueAntzH5I=": {
|
||||
"name": "RuOJsq/00lpPpPmx0pLVraCo//x44e7Nv4ueAntzH5I=",
|
||||
"value": {
|
||||
"Enabled": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"WgQAYbLKOTNzy865u021Vbj4bo5+dlMGzC2Tqh+INog=": {
|
||||
"name": "WgQAYbLKOTNzy865u021Vbj4bo5+dlMGzC2Tqh+INog=",
|
||||
"value": {
|
||||
"removePortraitMode": false,
|
||||
"recnet_deserialize_json_on_background_thread_length_threshold": 1000,
|
||||
"recnet_use_besthttp_request_queue": false,
|
||||
"parallelize_photon_connection": true,
|
||||
"start_critical_operations_early": true
|
||||
},
|
||||
"rule_id": "targetingGate",
|
||||
"group": "targetingGate",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"removePortraitMode"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1195813645",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "795959371",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7zLAnzZ4AiAvCAgX6xB3ZL:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"5aYKjIdHHgEJA9n+NDgHKVvg75gZCqPOVxV/LLL8xY8=": {
|
||||
"name": "5aYKjIdHHgEJA9n+NDgHKVvg75gZCqPOVxV/LLL8xY8=",
|
||||
"value": {
|
||||
"use_server_thumbnail": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_server_thumbnail"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Bu5L46Ir8t9UGFFQ3/Hc3gfNaMIjebJZsHZwzD/3UaU=": {
|
||||
"name": "Bu5L46Ir8t9UGFFQ3/Hc3gfNaMIjebJZsHZwzD/3UaU=",
|
||||
"value": {
|
||||
"rrui_create_invention_use_rrui_menu": false,
|
||||
"rrui_create_invention_tool_deprecating_title": "",
|
||||
"rrui_create_invention_tool_depricating_description": "",
|
||||
"rrui_invention_details_use_rrui_menu": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"rrui_create_invention_use_rrui_menu",
|
||||
"rrui_create_invention_tool_deprecating_title",
|
||||
"rrui_create_invention_tool_depricating_description"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MQBjO+9dg4LbRa9tlz7nvBxaNJmr3MFPRn2PDhhHotU=": {
|
||||
"name": "MQBjO+9dg4LbRa9tlz7nvBxaNJmr3MFPRn2PDhhHotU=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": false,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": false,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"improve_ui",
|
||||
"has_more_customization_access_points",
|
||||
"show_link_to_store",
|
||||
"show_featured_store_row",
|
||||
"featured_item_algo_endpoint",
|
||||
"merge_custom_and_fp_shirts",
|
||||
"hide_unowned_items"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"iayf7eJf/TklL9PeYh5/smZwdeBWepuPtO1I3thyKVM=": {
|
||||
"name": "iayf7eJf/TklL9PeYh5/smZwdeBWepuPtO1I3thyKVM=",
|
||||
"value": {
|
||||
"boot_sequence_implementation_version": 31
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"boot_sequence_implementation_version"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7/89aMEgKPgesfiqVex+VQDOS7oAgTYe8oNURFWhC7M=": {
|
||||
"name": "7/89aMEgKPgesfiqVex+VQDOS7oAgTYe8oNURFWhC7M=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": false,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "RRO Items",
|
||||
"use_new_filter_ordering": false,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "HotScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_unified_avatar_item_search",
|
||||
"UGC_search_text_title",
|
||||
"RRO_items_search_title",
|
||||
"use_new_filter_ordering",
|
||||
"look_up_gift_drop_from_local_cache",
|
||||
"CAI_search_includes_coach_items",
|
||||
"new_outfit_page_logic",
|
||||
"cai_default_ordering"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JKayJE0kgEzv4bEMggNJT4R2/iI8cG/9/uesn4ZcsaQ=": {
|
||||
"name": "JKayJE0kgEzv4bEMggNJT4R2/iI8cG/9/uesn4ZcsaQ=",
|
||||
"value": {
|
||||
"allow_jr_room_chat": true,
|
||||
"room_chat_toggle_position": "TopRight"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Top HUD Right",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"allow_jr_room_chat",
|
||||
"room_chat_toggle_position"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"F503KCev+4an+wI9tTiHOUA6JO2NSr5jH+s9dBPibko=": {
|
||||
"name": "F503KCev+4an+wI9tTiHOUA6JO2NSr5jH+s9dBPibko=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"improved_categorization",
|
||||
"improve_ui",
|
||||
"use_server_thumbnail",
|
||||
"add_token_counter"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"sz9DCNID27gHR/14V92UkjNh/H1qwX9qDIkK5PWWtS0=": {
|
||||
"name": "sz9DCNID27gHR/14V92UkjNh/H1qwX9qDIkK5PWWtS0=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing.BrandReorder.Control",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreClothing"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xOwWTXdtTq8TVsX6+4CxOeWy1YnIVFpUuRUbN6Y3YsA=": {
|
||||
"name": "xOwWTXdtTq8TVsX6+4CxOeWy1YnIVFpUuRUbN6Y3YsA=",
|
||||
"value": {
|
||||
"ShowNewAnnouncements": true,
|
||||
"ShowCloseButton": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New Announcement No Close Button",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowNewAnnouncements",
|
||||
"ShowCloseButton"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+FjsIrHruwBf2sofb3nNv39TYEkMCrR4PCCAwm6QcRE=": {
|
||||
"name": "+FjsIrHruwBf2sofb3nNv39TYEkMCrR4PCCAwm6QcRE=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_CommunityBoard",
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_PlayMenuTabs"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1iYZ8ROVvsFatfa8q52sM4ww5KahS8WnRismdUyKpEk=": {
|
||||
"name": "1iYZ8ROVvsFatfa8q52sM4ww5KahS8WnRismdUyKpEk=",
|
||||
"value": {
|
||||
"algo_name": "text-embedding-004",
|
||||
"algo_version": "0",
|
||||
"number_of_entities_to_return": 100,
|
||||
"minimum_similarity_score": 0,
|
||||
"minimum_offline_ranking_score": 0,
|
||||
"embedding_dimension": 768,
|
||||
"number_of_entities_to_shuffle": 0
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"algo_name",
|
||||
"algo_version",
|
||||
"embedding_dimension"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"5Ne2ZXd+6OKQrltRu/+8LhiIwbqUe7cOyWl5u5wyVMg=": {
|
||||
"name": "5Ne2ZXd+6OKQrltRu/+8LhiIwbqUe7cOyWl5u5wyVMg=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"qhj9uznFYK2+hHYVZyMqDexQoiXWI/M8ySIQDvo4TTY=": {
|
||||
"name": "qhj9uznFYK2+hHYVZyMqDexQoiXWI/M8ySIQDvo4TTY=",
|
||||
"value": {
|
||||
"CustomAvatarItemSets.DefaultItemSetItemsSelected": "false",
|
||||
"CustomAvatarItemSets.ItemTypeOrdering": [
|
||||
0,
|
||||
101,
|
||||
105,
|
||||
100,
|
||||
102,
|
||||
103,
|
||||
200,
|
||||
2,
|
||||
3,
|
||||
10,
|
||||
20,
|
||||
300,
|
||||
301
|
||||
],
|
||||
"CustomAvatarItemSets.NumItemsToShow": 5,
|
||||
"CustomAvatarItemSets.PanelTitleDynamic": "Popular Items in This Set"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CustomAvatarItemSets.DefaultItemSetItemsSelected"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"MxS/bT6Gc4KBCQnyzrx5F1dmKZD2yuAbKQrFHi5YJ3M=": {
|
||||
"name": "MxS/bT6Gc4KBCQnyzrx5F1dmKZD2yuAbKQrFHi5YJ3M=",
|
||||
"value": {
|
||||
"show_item_card_buy_button": true,
|
||||
"allow_resized_storefronts": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test: Show Buy Button",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_item_card_buy_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"jo0nio3aFnKFTY2nwIxXuhwSvaf6pYQRI7SPEgDBEMc=": {
|
||||
"name": "jo0nio3aFnKFTY2nwIxXuhwSvaf6pYQRI7SPEgDBEMc=",
|
||||
"value": {
|
||||
"show_new_consumable_features": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"cwETyDNPN8Lr7F6AT9niHEfW+MPDRoPHTeuP0xtcINs=": {
|
||||
"name": "cwETyDNPN8Lr7F6AT9niHEfW+MPDRoPHTeuP0xtcINs=",
|
||||
"value": {
|
||||
"DefaultToQuickChat": false,
|
||||
"ShowRoomChatRefresh": true,
|
||||
"RevampedSpeechBubbles": false,
|
||||
"show_party_chat_in_room_chat_menu": false,
|
||||
"should_autofocus": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowRoomChatRefresh"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hWtmqV+nI4zbYgzT6a3VbZ14u0xPV9xgdNVZ/YNvVdk=": {
|
||||
"name": "hWtmqV+nI4zbYgzT6a3VbZ14u0xPV9xgdNVZ/YNvVdk=",
|
||||
"value": {
|
||||
"DefaultToQuickChat": false,
|
||||
"ShowRoomChatRefresh": true,
|
||||
"RevampedSpeechBubbles": false,
|
||||
"show_party_chat_in_room_chat_menu": false,
|
||||
"should_autofocus": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"RevampedSpeechBubbles"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"45wqy0QITCnbP9E0XqlAgu20rTNoLGDsfbDSMMJU5Ak=": {
|
||||
"name": "45wqy0QITCnbP9E0XqlAgu20rTNoLGDsfbDSMMJU5Ak=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 4,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Quarter Size",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"reduce_imposter_size_divisor"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"cbpaQ6DNrJJz83qZgw+bh9pwYeVxI4aT6zdfnlIXzWE=": {
|
||||
"name": "cbpaQ6DNrJJz83qZgw+bh9pwYeVxI4aT6zdfnlIXzWE=",
|
||||
"value": {
|
||||
"invite_friends_for_free_room_joins": true,
|
||||
"invite_friends_for_invites": false,
|
||||
"show_rrui_notification_dialogs": true,
|
||||
"CreateInstance": true,
|
||||
"IsPublic": true,
|
||||
"show_rrui_game_invite_dialog": false,
|
||||
"show_decline_responses_game_invite_dialog": false,
|
||||
"show_rrui_announcements": true,
|
||||
"show_notification_for_new_friend_online": false,
|
||||
"new_friend_notification_quick_action_prompt": "",
|
||||
"ignore_version_check_for_invite_button": true,
|
||||
"notify_invitee_to_update_game": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ignore_version_check_for_invite_button",
|
||||
"notify_invitee_to_update_game"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"vJQREdlxb5iCvtoeYS1Aq/e+eT8CjiguN8agOuDwWE4=": {
|
||||
"name": "vJQREdlxb5iCvtoeYS1Aq/e+eT8CjiguN8agOuDwWE4=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ios_highend_min_physical_memory",
|
||||
"ios_highend_min_allocated_memory"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"P6nDVAJ1w8PI9qRFnfLCVFfTJ1170n5o0zXIr2vgyPc=": {
|
||||
"name": "P6nDVAJ1w8PI9qRFnfLCVFfTJ1170n5o0zXIr2vgyPc=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CanBulkGift"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"dOyPXdqwxRuH7TIf4wel4U/KmfLVFRhMb6xEx5HqOyo=": {
|
||||
"name": "dOyPXdqwxRuH7TIf4wel4U/KmfLVFRhMb6xEx5HqOyo=",
|
||||
"value": {
|
||||
"is_rich_chat_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1175906151",
|
||||
"gateValue": "true",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"kuyWaWR9qKanTkPEjWUY07ItCxv3a9Ub0qMghSznApo=": {
|
||||
"name": "kuyWaWR9qKanTkPEjWUY07ItCxv3a9Ub0qMghSznApo=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard_InkShowcaseRenamedMovedUpAndFeaturedMovedUp",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "PlayHighlight_InkShowcaseRenamedMovedUpAndFeaturedMovedUp",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "BothMovedUpAndShuffled",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_CommunityBoard"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"gi/dtnouEH5UflwgukpwV1scz5uFJEZWbFhCV6trG5E=": {
|
||||
"name": "gi/dtnouEH5UflwgukpwV1scz5uFJEZWbFhCV6trG5E=",
|
||||
"value": {
|
||||
"save_cached_view_mem": true,
|
||||
"ios_low_max_mirror_mp": 1,
|
||||
"enabled": false,
|
||||
"ios_low_max_mp": 1,
|
||||
"memoryless_aux_surfaces_sharecam": true,
|
||||
"memoryless_aux_surfaces_cachedviews": true,
|
||||
"memoryless_aux_surfaces_pip": false,
|
||||
"memoryless_aux_surfaces_sniper_scope": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "1MP",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ios_low_max_mirror_mp"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"4EIz8VtnXonOkpXDbys/2gIr9CQDWSK+uUb9YRtS7WI=": {
|
||||
"name": "4EIz8VtnXonOkpXDbys/2gIr9CQDWSK+uUb9YRtS7WI=",
|
||||
"value": {
|
||||
"remove_dance": false,
|
||||
"remove_reach": false,
|
||||
"remove_emote": false,
|
||||
"remove_emotes_from_room_chat": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"remove_reach"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"by2zZv2zclyRTBWzu+7NqleFOxsejEyR+nUnUHnJlaI=": {
|
||||
"name": "by2zZv2zclyRTBWzu+7NqleFOxsejEyR+nUnUHnJlaI=",
|
||||
"value": {
|
||||
"modern_bean": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"modern_bean"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GFmTPN3fORvkbQL7Ycg0X0qBQ8uuJGuzVlaAzRb2Op4=": {
|
||||
"name": "GFmTPN3fORvkbQL7Ycg0X0qBQ8uuJGuzVlaAzRb2Op4=",
|
||||
"value": {
|
||||
"save_cached_view_mem": true,
|
||||
"ios_low_max_mirror_mp": 1,
|
||||
"enabled": false,
|
||||
"ios_low_max_mp": 1,
|
||||
"memoryless_aux_surfaces_sharecam": true,
|
||||
"memoryless_aux_surfaces_cachedviews": true,
|
||||
"memoryless_aux_surfaces_pip": false,
|
||||
"memoryless_aux_surfaces_sniper_scope": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "1MP",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ios_low_max_mp"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ody8mylSuD2pzoOCw09kK3DGitXTYYoTqbCtzkPdXLs=": {
|
||||
"name": "ody8mylSuD2pzoOCw09kK3DGitXTYYoTqbCtzkPdXLs=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": true,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": true,
|
||||
"options_count_per_platform": {
|
||||
"mobile": 4,
|
||||
"pc": 4,
|
||||
"vr": 4
|
||||
},
|
||||
"AutoCompleteIndexName": "room-autocomplete_inc_popular_rooms"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_room_size_filter"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JfUSoNaEYZbx79FGJXjcrMms9UmekbDmmCwDEpljTy4=": {
|
||||
"name": "JfUSoNaEYZbx79FGJXjcrMms9UmekbDmmCwDEpljTy4=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"CCQGERbrMwffQjCDqeTiOs5RuKfXf4d6vUXQM6DOSH4=": {
|
||||
"name": "CCQGERbrMwffQjCDqeTiOs5RuKfXf4d6vUXQM6DOSH4=",
|
||||
"value": {
|
||||
"show_loading_screen_early": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hJrH4WZ3CXemqfxi4rdtjkWAnuS2QWg8jkSiBhuOpA8=": {
|
||||
"name": "hJrH4WZ3CXemqfxi4rdtjkWAnuS2QWg8jkSiBhuOpA8=",
|
||||
"value": {
|
||||
"voice_call_survey_canned_options": [
|
||||
"Couldn't Hear Others",
|
||||
"Others Couldn't Hear Me",
|
||||
"Voice Glitchy/Distorted",
|
||||
"Voices Cut Out/Break Up",
|
||||
"Voices Delayed/Lagged",
|
||||
"Voices Echo",
|
||||
"Other"
|
||||
],
|
||||
"room_gain_threshold": 0.55,
|
||||
"call_gain_threshold": 0.5,
|
||||
"mutually_exclusive_voice_source": true,
|
||||
"show_survey_when_leaving_call": true,
|
||||
"dynamic_switching_if_private": false,
|
||||
"current_group_name": "Show Survey",
|
||||
"survey_send_game_logs_bug_report": true,
|
||||
"voice_call_volume_percentage_default": 1,
|
||||
"show_voice_call_volume_slider": true,
|
||||
"should_deprecate_ugc_voice_channel": true,
|
||||
"Retention.IndividualVoiceVolume.Enabled": "",
|
||||
"is_individual_voice_volume_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"voice_call_volume_percentage_default",
|
||||
"show_voice_call_volume_slider",
|
||||
"show_survey_when_leaving_call"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "930626607",
|
||||
"gateValue": "true",
|
||||
"ruleID": "4gQtmddP2Gauv81AqWUi7a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"aF23sxttBOiFXOuFuesWqLjlamHN8iOUaUzs1eQxBTY=": {
|
||||
"name": "aF23sxttBOiFXOuFuesWqLjlamHN8iOUaUzs1eQxBTY=",
|
||||
"value": {
|
||||
"enabled": true,
|
||||
"button_label": "Make Your Own",
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome.WithTemplateCarousel",
|
||||
"button_icon": "clone_room_button_b"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test 3",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enabled",
|
||||
"button_label",
|
||||
"CuratedList_MobileHome",
|
||||
"button_icon"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"LzG3pAodIMOGqCr76UHzLiHd+TffyGTNLorawnJuubw=": {
|
||||
"name": "LzG3pAodIMOGqCr76UHzLiHd+TffyGTNLorawnJuubw=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_store_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GV3YSx8FWmgBbFavwe3cd5g/ONyQH1vdXAn5eA3LCP0=": {
|
||||
"name": "GV3YSx8FWmgBbFavwe3cd5g/ONyQH1vdXAn5eA3LCP0=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tryon",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": false,
|
||||
"ShowTokenStore": false,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": true,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowDormMirrorPurchaseCampaignButton"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "3817046945",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ztAWA33EVTnwSqCTzSJM6V4yh+WsayrQSLmRz4brIrs=": {
|
||||
"name": "ztAWA33EVTnwSqCTzSJM6V4yh+WsayrQSLmRz4brIrs=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ET5OeVZzLMxIdfXQZLipPC5G2Y3lbLBidBV+2X/q378=": {
|
||||
"name": "ET5OeVZzLMxIdfXQZLipPC5G2Y3lbLBidBV+2X/q378=",
|
||||
"value": {
|
||||
"online_ranking_configuration_name": "2025q2_personalized_all_items",
|
||||
"use_online_ranking": true,
|
||||
"number_of_entities_to_shuffle": 18,
|
||||
"use_contextual_features": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"online_ranking_configuration_name",
|
||||
"use_online_ranking",
|
||||
"number_of_entities_to_shuffle",
|
||||
"use_contextual_features"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"VfjnWFzmsdM3oPNZZjRR1gfHAWGJ3Tf+S+L8tIFMJyI=": {
|
||||
"name": "VfjnWFzmsdM3oPNZZjRR1gfHAWGJ3Tf+S+L8tIFMJyI=",
|
||||
"value": {
|
||||
"can_see_player_card": false,
|
||||
"can_see_rrp_card": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"can_see_player_card",
|
||||
"can_see_rrp_card"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1778358864",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"D85pbcj1oqQPo3MTT6TpYjJFxrjgghNrJ7oCP18yziM=": {
|
||||
"name": "D85pbcj1oqQPo3MTT6TpYjJFxrjgghNrJ7oCP18yziM=",
|
||||
"value": {
|
||||
"enabled": true,
|
||||
"button_label": "Make Your Own",
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"button_icon": "clone_room_button_b"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_MobileHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"n5LdornycU4VRzGBppiMLG2oeTMzW9p92D5+TdkHVaY=": {
|
||||
"name": "n5LdornycU4VRzGBppiMLG2oeTMzW9p92D5+TdkHVaY=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_MobileHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"5TKsiZ2n7cm5ebaVMCR43Tuzu2etqr6PZgoB/aT2MNY=": {
|
||||
"name": "5TKsiZ2n7cm5ebaVMCR43Tuzu2etqr6PZgoB/aT2MNY=",
|
||||
"value": {
|
||||
"CuratedList_PlayCategories": "Discovery.PageSource.PlayCategories"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BFUABL2Sw3BJcaABxEMbiFxPYudslje92iFtTJeLwhw=": {
|
||||
"name": "BFUABL2Sw3BJcaABxEMbiFxPYudslje92iFtTJeLwhw=",
|
||||
"value": {
|
||||
"show_new_layout": true,
|
||||
"show_new_colors": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Layout and Color",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bC3TJeVOwt4OUNLmQJkpi4g/o7e+WIHeJBWFTsOqJhY=": {
|
||||
"name": "bC3TJeVOwt4OUNLmQJkpi4g/o7e+WIHeJBWFTsOqJhY=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "AllCarousels_SimilarContent",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"home_menu_disable_horizontal_scroll",
|
||||
"open_content_menu",
|
||||
"view_more_button_text"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fKP/RUMhe+jNHfQ1RUhRag+yxhYfKFyIg4hKsL/2ofw=": {
|
||||
"name": "fKP/RUMhe+jNHfQ1RUhRag+yxhYfKFyIg4hKsL/2ofw=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"GroupName",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled",
|
||||
"ccu_load_synchronously",
|
||||
"abbreviate_cheer_counts"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Krua0nmm7s/IqnewxLW4Hfh6EHuYB49f5HVCcz+GjqI=": {
|
||||
"name": "Krua0nmm7s/IqnewxLW4Hfh6EHuYB49f5HVCcz+GjqI=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "targetingGate",
|
||||
"group": "targetingGate",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"reduce_imposter_size_divisor"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3602611043",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"TmW7t1aga+VhYToJKtTjqr9e7qbDy19dZr3YJ0Me9vw=": {
|
||||
"name": "TmW7t1aga+VhYToJKtTjqr9e7qbDy19dZr3YJ0Me9vw=",
|
||||
"value": {
|
||||
"show_playaction_button": false,
|
||||
"default_instance_type": "",
|
||||
"show_morelikethis_button": false,
|
||||
"supported_carousels": [],
|
||||
"new_rooms_threshold_days": 30,
|
||||
"hide_cheers_for_new": true,
|
||||
"hide_cheers_count": true,
|
||||
"show_cheers_data_token": false,
|
||||
"show_new_data_token": true,
|
||||
"loop_shimmer_animation": false,
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"show_publish_state": true,
|
||||
"Creator.Room.EnableRoomTokens": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_playaction_button",
|
||||
"default_instance_type",
|
||||
"supported_carousels"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"e9plvY+0UyWO7tYZrkIX3S0nK0aQoeiD92kbG2bcyc0=": {
|
||||
"name": "e9plvY+0UyWO7tYZrkIX3S0nK0aQoeiD92kbG2bcyc0=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Use Volume",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseVolumeBasedHipSlotsPickup"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"vAeCDd3TwDonadQtLbX96LBnzraFzFjt9zChVGwRP0g=": {
|
||||
"name": "vAeCDd3TwDonadQtLbX96LBnzraFzFjt9zChVGwRP0g=",
|
||||
"value": {
|
||||
"manipulate_enabled": true,
|
||||
"manipulate_collision_detection_enabled": true,
|
||||
"manipulate_touch_rot_degrees_per_inch": 120,
|
||||
"manipulate_toggle_enabled": false,
|
||||
"manipulate_quick_manipulate_enabled": false,
|
||||
"manipulate_disable_hand_collisions": false,
|
||||
"manipulate_cylindrical_placement_enabled": true,
|
||||
"manipulate_collision_outline_enabled": false,
|
||||
"manipulate_mp_surface_snap": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"manipulate_quick_manipulate_enabled",
|
||||
"manipulate_toggle_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"HGrJQ7tTnzVtOuiqlNRFb3zoVS5yehgoySHlLzbfwbk=": {
|
||||
"name": "HGrJQ7tTnzVtOuiqlNRFb3zoVS5yehgoySHlLzbfwbk=",
|
||||
"value": {
|
||||
"use_new_room_card": true,
|
||||
"use_new_club_event_card": true,
|
||||
"use_new_item_cards": true,
|
||||
"use_new_account_cards": true,
|
||||
"use_vnext_db_room_carousels": true
|
||||
},
|
||||
"rule_id": "6Utq2t47jKIzh3JZzKNSd4:override",
|
||||
"group": "6Utq2t47jKIzh3JZzKNSd4:override",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_vnext_db_room_carousels"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3668620673",
|
||||
"gateValue": "true",
|
||||
"ruleID": "5wzMtLjSXYmWPzy1fJq0BN"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cuxYdim7A/mWkA+hSQyHkgwSWznVLN21pUyBhEiojWM=": {
|
||||
"name": "cuxYdim7A/mWkA+hSQyHkgwSWznVLN21pUyBhEiojWM=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"can_save_for_later",
|
||||
"load_playmenu_tabs_from_server",
|
||||
"CuratedList_PlayMenuTabs"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xdjS+xpL36hCyjeqGPHFlUukym/R4nNuo4HuWha459Q=": {
|
||||
"name": "xdjS+xpL36hCyjeqGPHFlUukym/R4nNuo4HuWha459Q=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_PlayMenuTabs",
|
||||
"hide_global_room_search",
|
||||
"consolidate_play_top_bar",
|
||||
"show_filters_with_embedded_search"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"lte1XXsQcccbO/6tdbimCQA++3nvg0BTj3371qGt15A=": {
|
||||
"name": "lte1XXsQcccbO/6tdbimCQA++3nvg0BTj3371qGt15A=",
|
||||
"value": {
|
||||
"enable_retain_ui_state": true,
|
||||
"show_watch_button_in_menus": false,
|
||||
"portal_buttons_act_like_tabs": false,
|
||||
"portal_always_present": false,
|
||||
"enable_retain_cursor_position": true,
|
||||
"retain_ui_state_duration": 60,
|
||||
"use_dir_nav": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_watch_button_in_menus",
|
||||
"portal_buttons_act_like_tabs",
|
||||
"enable_retain_ui_state"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Fs7VFos/Hh4pwRi+Z1qkeJCYeVXLjHLL5mCKkrKUwvE=": {
|
||||
"name": "Fs7VFos/Hh4pwRi+Z1qkeJCYeVXLjHLL5mCKkrKUwvE=",
|
||||
"value": {
|
||||
"scalability_enabled": true,
|
||||
"scalability_preset": -1,
|
||||
"scalability_low_power": false,
|
||||
"scalability_scalemode": 4,
|
||||
"scalability_avatarenable": true,
|
||||
"scalability_textureenable": false,
|
||||
"scalability_shapesenable": true,
|
||||
"scalability_target_num_batches": 0,
|
||||
"scalability_max_triangles": 2000,
|
||||
"scalability_shapes_mem_delta": 400,
|
||||
"scalability_r1_allowed": -1,
|
||||
"scalability_r2_allowed": -1
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"scalability_enabled",
|
||||
"scalability_preset",
|
||||
"scalability_avatarenable",
|
||||
"scalability_textureenable",
|
||||
"scalability_shapesenable",
|
||||
"scalability_max_triangles",
|
||||
"scalability_shapes_mem_delta",
|
||||
"scalability_r1_allowed",
|
||||
"scalability_r2_allowed"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Wk/H+XrxWVJUcolSLx6A2ok7v6ZOxCgs/k7MaBs+Vmk=": {
|
||||
"name": "Wk/H+XrxWVJUcolSLx6A2ok7v6ZOxCgs/k7MaBs+Vmk=",
|
||||
"value": {
|
||||
"enable_retain_ui_state": true,
|
||||
"show_watch_button_in_menus": false,
|
||||
"portal_buttons_act_like_tabs": true,
|
||||
"portal_always_present": false,
|
||||
"enable_retain_cursor_position": true,
|
||||
"retain_ui_state_duration": 60,
|
||||
"use_dir_nav": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Retain state on",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_retain_ui_state",
|
||||
"portal_buttons_act_like_tabs",
|
||||
"show_watch_button_in_menus",
|
||||
"enable_retain_cursor_position"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"sfHO9RM1maAVhX6OXAg2Fi076IJydSZfFpnaEsBsBUI=": {
|
||||
"name": "sfHO9RM1maAVhX6OXAg2Fi076IJydSZfFpnaEsBsBUI=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"red_icon_when_muted",
|
||||
"orientation_sub_room_names"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"t2IhSxKNyXA6HiATweSVSW9QO3MKFtskikmqs3A9VwE=": {
|
||||
"name": "t2IhSxKNyXA6HiATweSVSW9QO3MKFtskikmqs3A9VwE=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHomeTestItemsForYouPos1",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test 1",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Be1HnSSh4GYNm5W85lueywzlMstB8ttjKPfa8ZRu5HA=": {
|
||||
"name": "Be1HnSSh4GYNm5W85lueywzlMstB8ttjKPfa8ZRu5HA=",
|
||||
"value": {
|
||||
"manipulate_enabled": true,
|
||||
"manipulate_collision_detection_enabled": true,
|
||||
"manipulate_touch_rot_degrees_per_inch": 120,
|
||||
"manipulate_toggle_enabled": true,
|
||||
"manipulate_quick_manipulate_enabled": false,
|
||||
"manipulate_disable_hand_collisions": false,
|
||||
"manipulate_cylindrical_placement_enabled": true,
|
||||
"manipulate_collision_outline_enabled": false,
|
||||
"manipulate_mp_surface_snap": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test - MP style",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"manipulate_mp_surface_snap",
|
||||
"manipulate_collision_outline_enabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"yVQ0WMIrRVAWh7/STEi2KnKQzmW/kO7nqxFjHvrFRec=": {
|
||||
"name": "yVQ0WMIrRVAWh7/STEi2KnKQzmW/kO7nqxFjHvrFRec=",
|
||||
"value": {
|
||||
"low_count": 5,
|
||||
"medium_count": 10,
|
||||
"high_count": 20,
|
||||
"GroupName": "2TierCooldown",
|
||||
"low_med_days": 1,
|
||||
"med_high_days": 7
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "2TierCooldown",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"BoD/20/XxwK7f6u5kfzXC5CfrdGtFnVzyQt6lOsvCXY=": {
|
||||
"name": "BoD/20/XxwK7f6u5kfzXC5CfrdGtFnVzyQt6lOsvCXY=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Has qol",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"has_dormskin_tab",
|
||||
"has_qol"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"OC2chF05UfLNegcFxvq03qm+mvTt3+AlqxRo798NemE=": {
|
||||
"name": "OC2chF05UfLNegcFxvq03qm+mvTt3+AlqxRo798NemE=",
|
||||
"value": {
|
||||
"ShowCategoriesTab": true,
|
||||
"ShowTransitionDialog": true,
|
||||
"GroupName": "Control"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GtWe2mijB3KRcTnDaLMQrk0kzqTP5lREP/Vjrj7RdWc=": {
|
||||
"name": "GtWe2mijB3KRcTnDaLMQrk0kzqTP5lREP/Vjrj7RdWc=",
|
||||
"value": {
|
||||
"show_trending_shirts_in_mirror": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"36O5uoDkTL6c79vjFrbs+S8nbdqKtq1ywaSLoTd/w84=": {
|
||||
"name": "36O5uoDkTL6c79vjFrbs+S8nbdqKtq1ywaSLoTd/w84=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 3500,
|
||||
"ios_highend_min_allocated_memory": 0,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ios_highend_min_physical_memory",
|
||||
"ios_highend_min_allocated_memory"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"HbR/Z4m2KwESMlbhd6bcanGnekQ+m2aHUbUiBrx8uhc=": {
|
||||
"name": "HbR/Z4m2KwESMlbhd6bcanGnekQ+m2aHUbUiBrx8uhc=",
|
||||
"value": {
|
||||
"preserve_top_bar_backpack": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "top_pocket_inventory_backpack",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BRpNNZ1gztCEJwROjFWiTRGLzt8qcYbpeG/AEo0axG4=": {
|
||||
"name": "BRpNNZ1gztCEJwROjFWiTRGLzt8qcYbpeG/AEo0axG4=",
|
||||
"value": {
|
||||
"third_person_feature_set": "FULL",
|
||||
"persistence": true,
|
||||
"Tip:Visibility": 0,
|
||||
"group_dance_with_gestures": true,
|
||||
"reduce_dance_options": false,
|
||||
"default_to_third_person": false,
|
||||
"toggle_in_orientation": true,
|
||||
"pinch_to_zoom": true,
|
||||
"tutorial_in_orientation": true,
|
||||
"new_melee": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "First - Toggle - Tutorial",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"default_to_third_person",
|
||||
"toggle_in_orientation",
|
||||
"pinch_to_zoom",
|
||||
"tutorial_in_orientation"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zzpK6ErQ/gmktrFqZ+2ei7qEqsyQTz8ES7ViSoYwzl8=": {
|
||||
"name": "zzpK6ErQ/gmktrFqZ+2ei7qEqsyQTz8ES7ViSoYwzl8=",
|
||||
"value": {
|
||||
"show_room_capacity_party_size_warning": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_room_capacity_party_size_warning"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"VdsZAYcRmyBGc8K/ZvTaqhDmYjwkwl3DKtBAttEqoL4=": {
|
||||
"name": "VdsZAYcRmyBGc8K/ZvTaqhDmYjwkwl3DKtBAttEqoL4=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BABGAZAPnx8WupDGf/bEgrlBBW6t4X5grXw3pIp2TuU=": {
|
||||
"name": "BABGAZAPnx8WupDGf/bEgrlBBW6t4X5grXw3pIp2TuU=",
|
||||
"value": {
|
||||
"manipulate_enabled": true,
|
||||
"manipulate_collision_detection_enabled": true,
|
||||
"manipulate_touch_rot_degrees_per_inch": 120,
|
||||
"manipulate_toggle_enabled": true,
|
||||
"manipulate_quick_manipulate_enabled": false,
|
||||
"manipulate_disable_hand_collisions": false,
|
||||
"manipulate_cylindrical_placement_enabled": true,
|
||||
"manipulate_collision_outline_enabled": true,
|
||||
"manipulate_mp_surface_snap": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"manipulate_cylindrical_placement_enabled",
|
||||
"manipulate_collision_outline_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"6eAhOMuxQWhRLbhGamr/pLWKLy9gf1p5VSF5sgrvCf4=": {
|
||||
"name": "6eAhOMuxQWhRLbhGamr/pLWKLy9gf1p5VSF5sgrvCf4=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": false,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"PurchaseReminderPrefabVariantOverride",
|
||||
"UseBackgroundImageForPurchaseReminderOverride",
|
||||
"Override_Content_Json"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"eEVu0ufq+mbMfr+9LQqwOnS+MGN83qc/9mFbqCn8OSs=": {
|
||||
"name": "eEVu0ufq+mbMfr+9LQqwOnS+MGN83qc/9mFbqCn8OSs=",
|
||||
"value": {
|
||||
"use_app_nav": false,
|
||||
"prevent_watch_home": false,
|
||||
"CuratedList_AppNavWidgetPanel": "Discovery.PageSource.AppNavWidgetPanel",
|
||||
"use_app_nav_forship": false,
|
||||
"enable_app_nav_tutorial": false,
|
||||
"enable_retain_ui_state": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_app_nav_forship",
|
||||
"CuratedList_AppNavWidgetPanel",
|
||||
"enable_app_nav_tutorial",
|
||||
"enable_retain_ui_state"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Y0qRjKsRSAtXUoHNmfh2MtUMJl6ccteIvXf8NezCy0g=": {
|
||||
"name": "Y0qRjKsRSAtXUoHNmfh2MtUMJl6ccteIvXf8NezCy0g=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zkT2gjneLDa012qJVh/nWqH3D3NK9O/jOI6UVPOlMnc=": {
|
||||
"name": "zkT2gjneLDa012qJVh/nWqH3D3NK9O/jOI6UVPOlMnc=",
|
||||
"value": {
|
||||
"ShareCam.VideoEnabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShareCam.VideoEnabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"JnXeZuI9OSLTj44GW4+d4pfLoqr1Mrq3fu9Qw3QcQBo=": {
|
||||
"name": "JnXeZuI9OSLTj44GW4+d4pfLoqr1Mrq3fu9Qw3QcQBo=",
|
||||
"value": {
|
||||
"use_curated_controls": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "RoomID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_curated_controls"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9BfRpqnRsE/7MrerrBIGFF30Qd+awjDVwSxSbcKDpHI=": {
|
||||
"name": "9BfRpqnRsE/7MrerrBIGFF30Qd+awjDVwSxSbcKDpHI=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"YSmK7hit2kTUkn1Upm1Z8EDy3S2BCMHOI0utn8TvkRI=": {
|
||||
"name": "YSmK7hit2kTUkn1Upm1Z8EDy3S2BCMHOI0utn8TvkRI=",
|
||||
"value": {
|
||||
"hide_ui": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hide_ui"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hdZKxaxNPh1UM/PXMck1PvxsRuH5BhrPz1sjATXj1CU=": {
|
||||
"name": "hdZKxaxNPh1UM/PXMck1PvxsRuH5BhrPz1sjATXj1CU=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"QqA1StF8oF0kAv1DJA2UTg46lvWwbsEc7kfnGPbialM=": {
|
||||
"name": "QqA1StF8oF0kAv1DJA2UTg46lvWwbsEc7kfnGPbialM=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"51h2mPgkIdPmz9MAPUOpSiRQh9pUW1uGnZ7obE1lHRI=": {
|
||||
"name": "51h2mPgkIdPmz9MAPUOpSiRQh9pUW1uGnZ7obE1lHRI=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"has_warning_banner"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"SknmSD5s9ibJBAEygupiUkPxnFgbfgG8+j6P4lKW5Gg=": {
|
||||
"name": "SknmSD5s9ibJBAEygupiUkPxnFgbfgG8+j6P4lKW5Gg=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"smallest_size",
|
||||
"mid_size",
|
||||
"largest_size"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"6wdFrteudJ05gwlweb1JptfO9N7QxZcz1XxYm1hIOZg=": {
|
||||
"name": "6wdFrteudJ05gwlweb1JptfO9N7QxZcz1XxYm1hIOZg=",
|
||||
"value": {
|
||||
"show_item_card_buy_button": true,
|
||||
"allow_resized_storefronts": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test: Allow Resizing",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"allow_resized_storefronts"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QFA1xV1mbKZ/J2fj80XSaYor34tBnauasIOvV26E/0o=": {
|
||||
"name": "QFA1xV1mbKZ/J2fj80XSaYor34tBnauasIOvV26E/0o=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"exclude_owned"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ifiZivEJOIMKkkLW+ASkhP9Vcdwn5mTyTLXMyn/huLg=": {
|
||||
"name": "ifiZivEJOIMKkkLW+ASkhP9Vcdwn5mTyTLXMyn/huLg=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"iosLowFPS",
|
||||
"iosHighFPS"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"N35FNq19IfySs8q8YgInUEMsqiLLbb2e8zdElrSBSNc=": {
|
||||
"name": "N35FNq19IfySs8q8YgInUEMsqiLLbb2e8zdElrSBSNc=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService",
|
||||
"WatchStoreCarouselsListPrefix",
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService",
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"pigAF+ZEi42QTSNfS7zQPBz6qVRgAN1OS8Z6hrG1UZk=": {
|
||||
"name": "pigAF+ZEi42QTSNfS7zQPBz6qVRgAN1OS8Z6hrG1UZk=",
|
||||
"value": {
|
||||
"show_playaction_button": false,
|
||||
"default_instance_type": "",
|
||||
"show_morelikethis_button": false,
|
||||
"supported_carousels": [],
|
||||
"new_rooms_threshold_days": 30,
|
||||
"hide_cheers_for_new": true,
|
||||
"hide_cheers_count": true,
|
||||
"show_cheers_data_token": false,
|
||||
"show_new_data_token": true,
|
||||
"loop_shimmer_animation": false,
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"show_publish_state": true,
|
||||
"Creator.Room.EnableRoomTokens": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_playaction_button",
|
||||
"default_instance_type",
|
||||
"supported_carousels"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Xe9Jood4ou+xVsSSlcH/eT/spQRTqapgEt3A9VMPcG8=": {
|
||||
"name": "Xe9Jood4ou+xVsSSlcH/eT/spQRTqapgEt3A9VMPcG8=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"create_tab_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"gyQjDCSqVJ4nZZXU47ZzysVXvxPBK1GrAMq5E8LoWcU=": {
|
||||
"name": "gyQjDCSqVJ4nZZXU47ZzysVXvxPBK1GrAMq5E8LoWcU=",
|
||||
"value": {
|
||||
"sort_criteria": "AppearsOnline"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "server_respecting_online",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"sort_criteria"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zdb2o75hf0qvnqJv/2vMEOvqXulcMfB+nX/DjZ8kIzU=": {
|
||||
"name": "zdb2o75hf0qvnqJv/2vMEOvqXulcMfB+nX/DjZ8kIzU=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": true,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": true,
|
||||
"options_count_per_platform": {
|
||||
"mobile": 4,
|
||||
"pc": 4,
|
||||
"vr": 4
|
||||
},
|
||||
"AutoCompleteIndexName": "room-autocomplete_inc_popular_rooms"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_search_rooms_text_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qiWdr29/Y5TivRGN36D6MUIPAXUcM4BQsUsSq5A7jGE=": {
|
||||
"name": "qiWdr29/Y5TivRGN36D6MUIPAXUcM4BQsUsSq5A7jGE=",
|
||||
"value": {
|
||||
"removePortraitMode": false,
|
||||
"recnet_deserialize_json_on_background_thread_length_threshold": 1000,
|
||||
"recnet_use_besthttp_request_queue": false,
|
||||
"parallelize_photon_connection": true,
|
||||
"start_critical_operations_early": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "1K Async",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"recnet_deserialize_json_on_background_thread_length_threshold"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "795959371",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7zLAnzZ4AiAvCAgX6xB3ZL:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pS+ndqvi0piOA8OqY0mybzOiw2L696L7UIT+0BNUFUs=": {
|
||||
"name": "pS+ndqvi0piOA8OqY0mybzOiw2L696L7UIT+0BNUFUs=",
|
||||
"value": {
|
||||
"useAlternateAvatar": true,
|
||||
"useNewLayout": true,
|
||||
"showOnlineFriends": true,
|
||||
"onlineFriendsViewAll": true,
|
||||
"newCallToAction": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"useAlternateAvatar"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"EhKe3sfpVjCI/oleEg+yub8BS1iXX29sqvaTbnSAGPE=": {
|
||||
"name": "EhKe3sfpVjCI/oleEg+yub8BS1iXX29sqvaTbnSAGPE=",
|
||||
"value": {
|
||||
"optimize_expresso_ui": false,
|
||||
"optimize_raycasts": false,
|
||||
"optimize_nearest_canvas": false,
|
||||
"cache_uri_route_strings": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"optimize_expresso_ui"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"WA4ZUiGzH4UfSJHMudIqR60LKWjyM6TsM0OR/OgS2Vw=": {
|
||||
"name": "WA4ZUiGzH4UfSJHMudIqR60LKWjyM6TsM0OR/OgS2Vw=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"setup_avatar_skeleton_dynamically"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"3uu5UR4UZXm8/vY/sRKYC0a7DOn5J8uBaiieliUqLiA=": {
|
||||
"name": "3uu5UR4UZXm8/vY/sRKYC0a7DOn5J8uBaiieliUqLiA=",
|
||||
"value": {
|
||||
"show_playaction_button": false,
|
||||
"default_instance_type": "",
|
||||
"show_morelikethis_button": false,
|
||||
"supported_carousels": [],
|
||||
"new_rooms_threshold_days": 30,
|
||||
"hide_cheers_for_new": true,
|
||||
"hide_cheers_count": true,
|
||||
"show_cheers_data_token": false,
|
||||
"show_new_data_token": true,
|
||||
"loop_shimmer_animation": false,
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"show_publish_state": true,
|
||||
"Creator.Room.EnableRoomTokens": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hide_cheers_count"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"5wGFVdz15vptqwRObl8kYl2FF1bfJa8owlGfE4Lf2dU=": {
|
||||
"name": "5wGFVdz15vptqwRObl8kYl2FF1bfJa8owlGfE4Lf2dU=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"NoNavBar"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ax1EQPZMpVRzb0BAerBHnBOYegNyfa94NBEwcpo+SLw=": {
|
||||
"name": "ax1EQPZMpVRzb0BAerBHnBOYegNyfa94NBEwcpo+SLw=",
|
||||
"value": {
|
||||
"disable_initial_scene_preload": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ubiOfGAg34XRqr5lCXS0AUSOl1fk28PN6M0Yg1RVRmI=": {
|
||||
"name": "ubiOfGAg34XRqr5lCXS0AUSOl1fk28PN6M0Yg1RVRmI=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test 1",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_equip_item_in_store_enabled",
|
||||
"gift_button_in_first_position_for_owned_item",
|
||||
"gift_button_is_primary_button",
|
||||
"equip_button_is_primary_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xLn0QOi9e3jktAfDQtg+c+A6ECzCnEl72ReOPoi6D+I=": {
|
||||
"name": "xLn0QOi9e3jktAfDQtg+c+A6ECzCnEl72ReOPoi6D+I=",
|
||||
"value": {
|
||||
"isEnabled": false,
|
||||
"cooldown_base": 86400,
|
||||
"cooldown_scale": 0,
|
||||
"cooldown_min": 86400,
|
||||
"cooldown_max": 604800,
|
||||
"cooldown_max_views": -1,
|
||||
"is_enabled_chat": true,
|
||||
"is_enabled_event": false,
|
||||
"is_enabled_gift": true
|
||||
},
|
||||
"rule_id": "2uVnVcb3eXYFjGGcyf0QFb:override",
|
||||
"group": "2uVnVcb3eXYFjGGcyf0QFb:override",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"cooldown_base",
|
||||
"cooldown_scale",
|
||||
"cooldown_min",
|
||||
"cooldown_max",
|
||||
"cooldown_max_views",
|
||||
"is_enabled_gift",
|
||||
"is_enabled_chat"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "930626607",
|
||||
"gateValue": "true",
|
||||
"ruleID": "4gQtmddP2Gauv81AqWUi7a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dV2YfBPxHbh8Zck4NkOBQWSppR3DwQ/4A2mnMAHzCBc=": {
|
||||
"name": "dV2YfBPxHbh8Zck4NkOBQWSppR3DwQ/4A2mnMAHzCBc=",
|
||||
"value": {
|
||||
"new_out_of_token_ui": true,
|
||||
"OutOfTokenSkuIds": [
|
||||
2,
|
||||
5,
|
||||
8,
|
||||
9,
|
||||
160,
|
||||
177,
|
||||
178,
|
||||
179,
|
||||
180
|
||||
],
|
||||
"ShowNewOutOfTokenPaths": true,
|
||||
"PopUpShopUsesRRUIFlow": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New Flows",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowNewOutOfTokenPaths"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"yWbAHeBPSdYJ04YqCeQYiOUTvopx7u0V3sTfhM+JPNk=": {
|
||||
"name": "yWbAHeBPSdYJ04YqCeQYiOUTvopx7u0V3sTfhM+JPNk=",
|
||||
"value": {
|
||||
"useOptimizedSchedulerUpdateQueues": false,
|
||||
"optimizeRbexSleep": false,
|
||||
"optimizeSyncParent": true,
|
||||
"slowdown_duration_ms": 0,
|
||||
"hitch_interval_ms": 0,
|
||||
"hitch_duration_ms": 0,
|
||||
"targetFramerate": 0,
|
||||
"maintain_fps": 0,
|
||||
"smallUpdateOptimizations2": true,
|
||||
"cullOldNetworkTransformSyncData": true,
|
||||
"opt_android_reliable_ticks": true,
|
||||
"optimizeQuestUmbraCulling": true,
|
||||
"optimizeDecals": true,
|
||||
"srp_batch_support_decals": true,
|
||||
"srp_batch_support_makerpen": true,
|
||||
"srp_batch_support_avatars": true,
|
||||
"srp_batch_healthbars": true,
|
||||
"optimizeFaceCameraUIAnchorSync": true,
|
||||
"useCachedViewPostprocessingOptimizations": true,
|
||||
"enableRRStudioCanvasCulling": true,
|
||||
"optimizeVoiceLinesController2D": true,
|
||||
"optimizeDistanceFadeManager": true,
|
||||
"enableCanvasDistanceCulling": true,
|
||||
"optimizeRbexSleepKinematicCheck": true,
|
||||
"optimizeSFXAndParticleReleaseMonitoring": true,
|
||||
"max_fixedUpdates_onMobile": 0,
|
||||
"optimizeTerrain": true,
|
||||
"optimizePhysicalTextUpdate": true,
|
||||
"optimizeConnectedGrabbableDecorationColliders": false,
|
||||
"optimize_fastgraphicraycaster_visibility": true,
|
||||
"disableUIAnchorFaceCameraForMirrors": true,
|
||||
"cullRemotePlayerMovementFeedbackParticles": true,
|
||||
"optimizeScreenInputHandlerUpdateStateForVR": true,
|
||||
"enableMuzzleFlashDistanceCulling": true,
|
||||
"fixAudioDistanceChecks": false,
|
||||
"optimize_avatar_network_interpolation": true,
|
||||
"useMirrorOcclusionCulling": true,
|
||||
"useNametagNonUGUI": true,
|
||||
"forceIOSHitching": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"useOptimizedSchedulerUpdateQueues",
|
||||
"optimizeSyncParent",
|
||||
"opt_android_reliable_ticks",
|
||||
"smallUpdateOptimizations2",
|
||||
"srp_batch_support_avatars",
|
||||
"srp_batch_healthbars",
|
||||
"srp_batch_support_makerpen",
|
||||
"useNametagNonUGUI",
|
||||
"optimizeVoiceLinesController2D",
|
||||
"optimizeDistanceFadeManager",
|
||||
"cullOldNetworkTransformSyncData",
|
||||
"optimizeScreenInputHandlerUpdateStateForVR",
|
||||
"enableMuzzleFlashDistanceCulling",
|
||||
"optimizeSFXAndParticleReleaseMonitoring",
|
||||
"disableUIAnchorFaceCameraForMirrors",
|
||||
"optimizeFaceCameraUIAnchorSync",
|
||||
"optimizeConnectedGrabbableDecorationColliders",
|
||||
"optimizeQuestUmbraCulling",
|
||||
"optimize_avatar_network_interpolation",
|
||||
"useMirrorOcclusionCulling",
|
||||
"fixAudioDistanceChecks",
|
||||
"optimizeRbexSleep",
|
||||
"optimizeTerrain",
|
||||
"srp_batch_support_decals",
|
||||
"optimizePhysicalTextUpdate",
|
||||
"cullRemotePlayerMovementFeedbackParticles",
|
||||
"optimizeRbexSleepKinematicCheck",
|
||||
"optimize_fastgraphicraycaster_visibility",
|
||||
"enableCanvasDistanceCulling",
|
||||
"enableRRStudioCanvasCulling",
|
||||
"useCachedViewPostprocessingOptimizations",
|
||||
"optimizeDecals"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0p+4kxX2Sq2F0hSmOz+j8Uv+4oV+KXZ6uswwwqhDABg=": {
|
||||
"name": "0p+4kxX2Sq2F0hSmOz+j8Uv+4oV+KXZ6uswwwqhDABg=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Shirts",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_unified_avatar_item_search",
|
||||
"UGC_search_text_title",
|
||||
"RRO_items_search_title",
|
||||
"use_new_filter_ordering",
|
||||
"CAI_search_includes_coach_items",
|
||||
"extra_purchasable_item_ids_in_default_search"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"FaxsiUhsLYSp2e5JVyk/ydJhXnZv5zKPx6B4VHzZkBI=": {
|
||||
"name": "FaxsiUhsLYSp2e5JVyk/ydJhXnZv5zKPx6B4VHzZkBI=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured.UnifiedItemsForYou",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"o5B41u5atn+eXrJa45B5+VuvGvxiZijHBNb/ijQ2r3k=": {
|
||||
"name": "o5B41u5atn+eXrJa45B5+VuvGvxiZijHBNb/ijQ2r3k=",
|
||||
"value": {
|
||||
"enable_retain_ui_state": true,
|
||||
"show_watch_button_in_menus": false,
|
||||
"portal_buttons_act_like_tabs": false,
|
||||
"portal_always_present": false,
|
||||
"enable_retain_cursor_position": true,
|
||||
"retain_ui_state_duration": 60,
|
||||
"use_dir_nav": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_retain_cursor_position",
|
||||
"enable_retain_ui_state",
|
||||
"retain_ui_state_duration"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hqpcLMNjekHFU3mRdZHMW/G91lktKcKfy+zL1LvqCgk=": {
|
||||
"name": "hqpcLMNjekHFU3mRdZHMW/G91lktKcKfy+zL1LvqCgk=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"rtqkSFlk4e+5BHjDPHUnDJCpQki+SNHhu9PnJJAb3C0=": {
|
||||
"name": "rtqkSFlk4e+5BHjDPHUnDJCpQki+SNHhu9PnJJAb3C0=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"YTZjAhRbU1kdlLpa7bfJDWCkRFH3k/9TUwn7iBlG4Rw=": {
|
||||
"name": "YTZjAhRbU1kdlLpa7bfJDWCkRFH3k/9TUwn7iBlG4Rw=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"target_orientation_room_name",
|
||||
"orientation_version",
|
||||
"orientation_sub_room_names"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0EtX21/vav1WndCh2jMrfR7l0EkcA2e4pBeku4jysaU=": {
|
||||
"name": "0EtX21/vav1WndCh2jMrfR7l0EkcA2e4pBeku4jysaU=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"LlwkS3e7I3to+WHtXz0hUdFMBTFfMeg7Dqv8GsC2kt8=": {
|
||||
"name": "LlwkS3e7I3to+WHtXz0hUdFMBTFfMeg7Dqv8GsC2kt8=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": false,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "NewWatchOnly",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_watch_architecture_enabled",
|
||||
"rrui_notifications_enabled",
|
||||
"rrui_people_menu_enabled",
|
||||
"home_search_bar_enabled",
|
||||
"home_larger_main_menu_enabled",
|
||||
"home_play_button_alt_icon_enabled",
|
||||
"carousel_order"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"8/YcSpoRQlclif0qXMqUH1Wuu6P0+TI9nqSy3ZG+jYw=": {
|
||||
"name": "8/YcSpoRQlclif0qXMqUH1Wuu6P0+TI9nqSy3ZG+jYw=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"router_prefill_group",
|
||||
"fallback_router_prefill_group"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"RKgYt9qVGSboEyEUmp4W61SPfQis9pbpvejxKndwglg=": {
|
||||
"name": "RKgYt9qVGSboEyEUmp4W61SPfQis9pbpvejxKndwglg=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"store_item_prefab_variant_name",
|
||||
"invention_prefab_variant_name",
|
||||
"custom_avatar_item_prefab_variant_name",
|
||||
"ad_carousel_prefab_variant_name"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7tGKXABIGNK/qxfvX0ccJ6gPXzaS1msG9RpVZQm1Ptk=": {
|
||||
"name": "7tGKXABIGNK/qxfvX0ccJ6gPXzaS1msG9RpVZQm1Ptk=",
|
||||
"value": {
|
||||
"use_follow_constraint": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_follow_constraint"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"3EGTCa+OgI+VYj0cFpKuEtdTwNiSHCRZ6EpdX5N7yPg=": {
|
||||
"name": "3EGTCa+OgI+VYj0cFpKuEtdTwNiSHCRZ6EpdX5N7yPg=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "No Limit",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CanBulkPurchaseConsumables",
|
||||
"CanBulkGiftConsumables",
|
||||
"IndividualBulkGiftCap"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ol1kv9HfxUQ/cJ91TS7ydk4wJhDFoHzlNU4LrXD7O5o=": {
|
||||
"name": "Ol1kv9HfxUQ/cJ91TS7ydk4wJhDFoHzlNU4LrXD7O5o=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"conditionally_spawn_progression_events_intro_banner",
|
||||
"conditionally_spawn_dev_tools"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GoEpRu6gfd71aFPefkUXRaXC5MS2pdYaB5xXl084/wI=": {
|
||||
"name": "GoEpRu6gfd71aFPefkUXRaXC5MS2pdYaB5xXl084/wI=",
|
||||
"value": {
|
||||
"use_new_room_card": true,
|
||||
"use_new_club_event_card": true,
|
||||
"use_new_item_cards": true,
|
||||
"use_new_account_cards": true,
|
||||
"use_vnext_db_room_carousels": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New Account Cards",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_new_account_cards"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"MURtlFbC+DfBaNx9RD5rH/CZLQgBuhz0OdEASH6HAkM=": {
|
||||
"name": "MURtlFbC+DfBaNx9RD5rH/CZLQgBuhz0OdEASH6HAkM=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"q3k6nABhLP9sZOVsILVhPDM0HW1OvVb7MvvmBwybGns=": {
|
||||
"name": "q3k6nABhLP9sZOVsILVhPDM0HW1OvVb7MvvmBwybGns=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "No Trending Tags",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"remove_trending_tags_chips",
|
||||
"rooms_grid_view_card_limit"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"5sSCAfb2TDlMNUO18+20DbUKiI87uw8FdajWwtXZKfE=": {
|
||||
"name": "5sSCAfb2TDlMNUO18+20DbUKiI87uw8FdajWwtXZKfE=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": false,
|
||||
"show_link_to_store": false,
|
||||
"merge_custom_and_fp_shirts": false,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hide_unowned_items",
|
||||
"show_link_to_store",
|
||||
"merge_custom_and_fp_shirts"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hftZtRHKvnGQqKTbEDGUUf6PGtPoQ2UAuJoIn9LyZuM=": {
|
||||
"name": "hftZtRHKvnGQqKTbEDGUUf6PGtPoQ2UAuJoIn9LyZuM=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_categorization_improvements_store_v2"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ej+LIDgphudg+iA0W6BRBMjtgz10/+SYbpx3Q6oB0pM=": {
|
||||
"name": "Ej+LIDgphudg+iA0W6BRBMjtgz10/+SYbpx3Q6oB0pM=",
|
||||
"value": {
|
||||
"show_party_card": true,
|
||||
"allow_goto_private_instance": true,
|
||||
"show_leave_party_button_on_room_change": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test #2",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_party_card",
|
||||
"allow_goto_private_instance",
|
||||
"show_leave_party_button_on_room_change"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"AhzmV3XM+aXbxhZeJgMHTYALWc3WRIVVgygB276GmgU=": {
|
||||
"name": "AhzmV3XM+aXbxhZeJgMHTYALWc3WRIVVgygB276GmgU=",
|
||||
"value": {
|
||||
"IsSearchWithTagsEnabled": true,
|
||||
"UseElasticsearch": true,
|
||||
"UseSecondaryOrdering": true,
|
||||
"UseFuzzySearch": true,
|
||||
"SearchDescription": false,
|
||||
"NameRelevanceBoost": 1,
|
||||
"TagRelevanceBoost": 1,
|
||||
"DescriptionRelevanceBoost": 1
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseFuzzySearch",
|
||||
"UseElasticsearch",
|
||||
"SearchDescription"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1ya/HYWycDxyaFktC+uYI6x1ruEZ6XQ8LBbWlopo24Y=": {
|
||||
"name": "1ya/HYWycDxyaFktC+uYI6x1ruEZ6XQ8LBbWlopo24Y=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"canned_chat_room_mapping"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+VLdtkKJYcQgfTgPDQtSQHRiHqKwN/U5LwFbwap4aGU=": {
|
||||
"name": "+VLdtkKJYcQgfTgPDQtSQHRiHqKwN/U5LwFbwap4aGU=",
|
||||
"value": {
|
||||
"card_variant_name": "small_no_label",
|
||||
"limited_badge_enabled": true,
|
||||
"limited_badge_details": true,
|
||||
"limited_time_display_days": 0,
|
||||
"limited_badge_threshold_days": 7
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "No Label",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"card_variant_name"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0c6AHtkrVoQkO/kl5mvQzg8c9fRlYai7hzj/JuidU4M=": {
|
||||
"name": "0c6AHtkrVoQkO/kl5mvQzg8c9fRlYai7hzj/JuidU4M=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QtLBQtAcW/FRA9Y4b3St3/Y/UtgWPfQxUhT3G7NQfh0=": {
|
||||
"name": "QtLBQtAcW/FRA9Y4b3St3/Y/UtgWPfQxUhT3G7NQfh0=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"campaignIds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Gp4DmKWH66oJWnf33T+SD7ZvUZzlJzYPlU/mN/5uKu8=": {
|
||||
"name": "Gp4DmKWH66oJWnf33T+SD7ZvUZzlJzYPlU/mN/5uKu8=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JqQc8o9mBrFEENiJwy50Mo5WdTKHgq1yYT35NUJMxsg=": {
|
||||
"name": "JqQc8o9mBrFEENiJwy50Mo5WdTKHgq1yYT35NUJMxsg=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"has_button",
|
||||
"has_dormskin_tab"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"o9YNEMuPVz3iYjcKq1g0mUhlWinKFWN20KcR0QqKCF4=": {
|
||||
"name": "o9YNEMuPVz3iYjcKq1g0mUhlWinKFWN20KcR0QqKCF4=",
|
||||
"value": {
|
||||
"EnabledFlag": 0,
|
||||
"MatchmakingUI.ShowIfEveryoneLeaves": "false"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"EnabledFlag",
|
||||
"MatchmakingUI.ShowIfEveryoneLeaves"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"LFmF4x/ZWteYpKwvXh9GP+ZtfMeQLvrNv9fVDxzzBlQ=": {
|
||||
"name": "LFmF4x/ZWteYpKwvXh9GP+ZtfMeQLvrNv9fVDxzzBlQ=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"imposter_format"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Z7N0oa1Ltr9pF7bh00at7ft4YBe+4vCRdhiXSBa98E4=": {
|
||||
"name": "Z7N0oa1Ltr9pF7bh00at7ft4YBe+4vCRdhiXSBa98E4=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"EnableDownloadableAvatarItems"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "101598084",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tcLIdDl4Qoe7RWLF3TFzFTbIl40iFTvgiUM93qNssos=": {
|
||||
"name": "tcLIdDl4Qoe7RWLF3TFzFTbIl40iFTvgiUM93qNssos=",
|
||||
"value": {
|
||||
"show_x_to_close": true,
|
||||
"show_ignore_for_now": false,
|
||||
"improve_room_chat_visuals": true,
|
||||
"add_room_chat_emojis": true,
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat": "false",
|
||||
"max_room_chat_preview_lines": 5,
|
||||
"max_room_chat_expanded_lines": 21,
|
||||
"show_select_unselect_all_button": false,
|
||||
"preselect_all_accounts": false,
|
||||
"show_modified_dont_invite_button": true,
|
||||
"show_modified_invite_button": false,
|
||||
"emphasize_reccenter_button": false,
|
||||
"should_autofocus": true,
|
||||
"should_autofocus_search": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Modified Dont Invite Button",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_select_unselect_all_button",
|
||||
"preselect_all_accounts",
|
||||
"show_modified_dont_invite_button",
|
||||
"show_modified_invite_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"yoZlA/FMTGronWh/i5K8YxTIQ2ZmoEVUY233BwGvRqw=": {
|
||||
"name": "yoZlA/FMTGronWh/i5K8YxTIQ2ZmoEVUY233BwGvRqw=",
|
||||
"value": {
|
||||
"IsCustomDormForNewUsersEnabled": false,
|
||||
"IsCustomDormCustomizationLocked": false,
|
||||
"ShowNewTutorial": false,
|
||||
"ShowTutorialOnSpawn": false,
|
||||
"IsDormSlotsVisible": true,
|
||||
"ShowDiscoveryNotification": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Legacy Dorm",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"IsCustomDormForNewUsersEnabled",
|
||||
"IsCustomDormCustomizationLocked"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Fj4CZd1lz5QOCEZZ2n0V8GaCnRHj6FzIrnjvyQJqG34=": {
|
||||
"name": "Fj4CZd1lz5QOCEZZ2n0V8GaCnRHj6FzIrnjvyQJqG34=",
|
||||
"value": {
|
||||
"third_person_feature_set": "FULL",
|
||||
"persistence": true,
|
||||
"Tip:Visibility": 0,
|
||||
"group_dance_with_gestures": true,
|
||||
"reduce_dance_options": false,
|
||||
"default_to_third_person": false,
|
||||
"toggle_in_orientation": true,
|
||||
"pinch_to_zoom": true,
|
||||
"tutorial_in_orientation": true,
|
||||
"new_melee": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Paged Radial",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"group_dance_with_gestures",
|
||||
"reduce_dance_options"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1o+EQckxIclnokvuZsIu6U/bzUeeV8vcL8zcsnBWYyM=": {
|
||||
"name": "1o+EQckxIclnokvuZsIu6U/bzUeeV8vcL8zcsnBWYyM=",
|
||||
"value": {
|
||||
"canned_chat_RecRoomDefault": [
|
||||
"Hi!",
|
||||
"Bye!",
|
||||
"Ready?",
|
||||
"BRB",
|
||||
"Let’s go!"
|
||||
],
|
||||
"canned_chat_RoomDefault": [
|
||||
"Come with me",
|
||||
"Help!",
|
||||
"Let's party up!",
|
||||
"Let's be friends!",
|
||||
"May I pick the room?"
|
||||
],
|
||||
"canned_chat_CustomA": [
|
||||
"Yes",
|
||||
"OK",
|
||||
"Thanks",
|
||||
"LOL",
|
||||
"You look great!"
|
||||
],
|
||||
"canned_chat_CustomB": [
|
||||
"No",
|
||||
"Boo",
|
||||
"Sorry",
|
||||
"I don’t like this",
|
||||
"What the coach?!"
|
||||
],
|
||||
"room_chat_bubble_use_revamped_ui": true,
|
||||
"room_chat_bubble_font_size_mult": 1.5,
|
||||
"room_chat_bubble_bg_color": "#ffffffaa",
|
||||
"room_chat_bubble_text_color": "black",
|
||||
"room_chat_bubble_max_emoji_embiggen_len": 1,
|
||||
"room_chat_bubble_embiggen_mult": 2,
|
||||
"room_chat_bubble_should_embolden": true,
|
||||
"room_chat_bubble_text_alpha": [
|
||||
1,
|
||||
0.8,
|
||||
0.6
|
||||
],
|
||||
"room_chat_bubble_bg_alpha": [
|
||||
0.66,
|
||||
0.5,
|
||||
0.3
|
||||
],
|
||||
"room_chat_canned_detached_mode": "right",
|
||||
"show_party_chat_in_room_chat_menu": true,
|
||||
"enable_party_voice_toggle_in_room_chat": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Size and Emoji and Color White",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"room_chat_bubble_use_revamped_ui",
|
||||
"room_chat_bubble_font_size_mult",
|
||||
"room_chat_bubble_bg_color",
|
||||
"room_chat_bubble_text_color",
|
||||
"room_chat_bubble_max_emoji_embiggen_len",
|
||||
"room_chat_bubble_embiggen_mult",
|
||||
"room_chat_bubble_should_embolden",
|
||||
"room_chat_bubble_text_alpha",
|
||||
"room_chat_bubble_bg_alpha"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ig1MOZ2K0ZSYvhD+GM6CL12PiwBT36kmJ21OUt6QtHQ=": {
|
||||
"name": "Ig1MOZ2K0ZSYvhD+GM6CL12PiwBT36kmJ21OUt6QtHQ=",
|
||||
"value": {
|
||||
"useAlternateAvatar": true,
|
||||
"useNewLayout": true,
|
||||
"showOnlineFriends": true,
|
||||
"onlineFriendsViewAll": true,
|
||||
"newCallToAction": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "layout_friends_view_all",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"useNewLayout",
|
||||
"useAlternateAvatar",
|
||||
"showOnlineFriends",
|
||||
"onlineFriendsViewAll",
|
||||
"newCallToAction"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Wrk2ppvZ7LKcf1n1XTGrumQYr9W37DRwJTD/0u+a6XI=": {
|
||||
"name": "Wrk2ppvZ7LKcf1n1XTGrumQYr9W37DRwJTD/0u+a6XI=",
|
||||
"value": {
|
||||
"force_besthttp_typeload": false,
|
||||
"delay_recnet_connect": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"force_besthttp_typeload"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"kz4cvWgVPo/9FhIMmidhMR6AnWXYV3TruI8/Bowfw9k=": {
|
||||
"name": "kz4cvWgVPo/9FhIMmidhMR6AnWXYV3TruI8/Bowfw9k=",
|
||||
"value": {
|
||||
"srp_batcher_enabled_for_shapes": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "SRP Batcher Enabled",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Wch/xTlLfkH7lgsZfD2xLea5mttq5eMa+0TcNZ72zKc=": {
|
||||
"name": "Wch/xTlLfkH7lgsZfD2xLea5mttq5eMa+0TcNZ72zKc=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"has_more_customization_access_points"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"i8IroSPdvP48BW+NdFDtKq3bkYMKOIKXrMPI/DOLAC8=": {
|
||||
"name": "i8IroSPdvP48BW+NdFDtKq3bkYMKOIKXrMPI/DOLAC8=",
|
||||
"value": {
|
||||
"MaxRoomCount": 10,
|
||||
"Discovery.GenreBasedMatchmaking.FreshnessParameter": 5,
|
||||
"JoinOrCreateRoomCount": 10,
|
||||
"AdditionalJoinOnlyRoomCount": 15
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Size10",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"MaxRoomCount"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"mWD6Drp5tfyWeJecPOgPRiTOMKQ9GLoQni0lMOSz+AM=": {
|
||||
"name": "mWD6Drp5tfyWeJecPOgPRiTOMKQ9GLoQni0lMOSz+AM=",
|
||||
"value": {
|
||||
"show_reactions": true,
|
||||
"show_emotes": true,
|
||||
"reactions": [
|
||||
"👍",
|
||||
"❤️",
|
||||
"🎉",
|
||||
"😭:Sad",
|
||||
"👎",
|
||||
"😲:Shocked",
|
||||
"💀",
|
||||
"🤣:Laugh"
|
||||
],
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [],
|
||||
"show_react_gesture_tip": false,
|
||||
"fade_react_joystick": false,
|
||||
"show_react_joystick": false,
|
||||
"react_gesture_tip": "",
|
||||
"show_react_radial_joystick": false,
|
||||
"custom_local_profile": false,
|
||||
"show_profile_blocklist": false,
|
||||
"hide_old_set_badges": false,
|
||||
"Tip:Visibility": 0,
|
||||
"snowball_enabled": false,
|
||||
"snowball_feedback_sizing": false,
|
||||
"snowball_feedback_vfx": false,
|
||||
"snowball_feedback_room_chat": false,
|
||||
"snowball_feedback_notification": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"snowball_enabled",
|
||||
"snowball_feedback_sizing",
|
||||
"snowball_feedback_vfx",
|
||||
"snowball_feedback_notification",
|
||||
"snowball_feedback_room_chat"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"mjp7On6b9MqQbqsmxPTn76EDTY1kLXN8CAMNU+JVvDg=": {
|
||||
"name": "mjp7On6b9MqQbqsmxPTn76EDTY1kLXN8CAMNU+JVvDg=",
|
||||
"value": {
|
||||
"voice_call_survey_canned_options": [
|
||||
"Couldn't Hear Others",
|
||||
"Others Couldn't Hear Me",
|
||||
"Voice Glitchy/Distorted",
|
||||
"Voices Cut Out/Break Up",
|
||||
"Voices Delayed/Lagged",
|
||||
"Voices Echo",
|
||||
"Other"
|
||||
],
|
||||
"room_gain_threshold": 0.55,
|
||||
"call_gain_threshold": 0.5,
|
||||
"mutually_exclusive_voice_source": true,
|
||||
"show_survey_when_leaving_call": true,
|
||||
"dynamic_switching_if_private": false,
|
||||
"current_group_name": "Show Survey",
|
||||
"survey_send_game_logs_bug_report": true,
|
||||
"voice_call_volume_percentage_default": 1,
|
||||
"show_voice_call_volume_slider": true,
|
||||
"should_deprecate_ugc_voice_channel": true,
|
||||
"Retention.IndividualVoiceVolume.Enabled": "",
|
||||
"is_individual_voice_volume_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Tuned Thresholds",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"room_gain_threshold",
|
||||
"call_gain_threshold",
|
||||
"mutually_exclusive_voice_source",
|
||||
"dynamic_switching_if_private",
|
||||
"show_survey_when_leaving_call"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"f+uv2EFp87TdOu3tLcD+IdFm359wYh/Ek3DnMvlU7s4=": {
|
||||
"name": "f+uv2EFp87TdOu3tLcD+IdFm359wYh/Ek3DnMvlU7s4=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": true,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Improved UI without token counter",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"improve_ui",
|
||||
"use_server_thumbnail",
|
||||
"add_token_counter"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Y76QF4PdhqaCboBBvBjmUidm1SiFhPGSKNKgexKYCKQ=": {
|
||||
"name": "Y76QF4PdhqaCboBBvBjmUidm1SiFhPGSKNKgexKYCKQ=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight.PartyHUB.Control",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bEdfFh5ilqBAE8j4OwAyoBksIiaR74Lo8bXEA2NzV8I=": {
|
||||
"name": "bEdfFh5ilqBAE8j4OwAyoBksIiaR74Lo8bXEA2NzV8I=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"/3YDBq8Cpb6ar8Zy2px3o3HLQAF09cZRwRQB0DEnfA4=": {
|
||||
"name": "/3YDBq8Cpb6ar8Zy2px3o3HLQAF09cZRwRQB0DEnfA4=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_potato_avatar"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"FH1HJsV9VjxAZPLjQQTm+IJl652KdwKDZmGBVVBLQSg=": {
|
||||
"name": "FH1HJsV9VjxAZPLjQQTm+IJl652KdwKDZmGBVVBLQSg=",
|
||||
"value": {
|
||||
"is_in_test": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_in_test"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1lTq6EqOtTc/N5bAS9938bVmcR091OW8KCxPtJT8xk4=": {
|
||||
"name": "1lTq6EqOtTc/N5bAS9938bVmcR091OW8KCxPtJT8xk4=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"wM7pAd+op0QOfG8DsYmHsp0fR5cJBvulRo+6Ty+rjH8=": {
|
||||
"name": "wM7pAd+op0QOfG8DsYmHsp0fR5cJBvulRo+6Ty+rjH8=",
|
||||
"value": {
|
||||
"enable_dir_nav": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_dir_nav"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BLNL5ggwJdYgyU0ENC35qR49TEiqsVf7mR5QpvLR+n4=": {
|
||||
"name": "BLNL5ggwJdYgyU0ENC35qR49TEiqsVf7mR5QpvLR+n4=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"yYLQVw6HPZlG6LOjgGlDgBNYfIkSDSxNn4v2QXsyJl4=": {
|
||||
"name": "yYLQVw6HPZlG6LOjgGlDgBNYfIkSDSxNn4v2QXsyJl4=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayMenuTabs",
|
||||
"hide_global_room_search",
|
||||
"consolidate_play_top_bar",
|
||||
"show_filters_with_embedded_search"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+tndnQjZ0ZwLHs8IBc3l/itZDzfM3eZnZ/buZNNJ0Nk=": {
|
||||
"name": "+tndnQjZ0ZwLHs8IBc3l/itZDzfM3eZnZ/buZNNJ0Nk=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"H1IUXLRovhZvFRFnR9e03CoqswgX0OjjJdHONzuDwGU=": {
|
||||
"name": "H1IUXLRovhZvFRFnR9e03CoqswgX0OjjJdHONzuDwGU=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"energy_mode_0_cost_scale_factor"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"R4ZU1UI+1mOCXHMNPfwyrQLGg16UN3Zw7zNF2bd2k0c=": {
|
||||
"name": "R4ZU1UI+1mOCXHMNPfwyrQLGg16UN3Zw7zNF2bd2k0c=",
|
||||
"value": {
|
||||
"direct_matchmaking": false,
|
||||
"CuratedList_PlayCategories": "Discovery.PageSource.PlayCategories"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"direct_matchmaking",
|
||||
"CuratedList_PlayCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"B/XiHjCTYjEG9GrZrggjbx+9OfMLAh2rBXfxQKH2JJU=": {
|
||||
"name": "B/XiHjCTYjEG9GrZrggjbx+9OfMLAh2rBXfxQKH2JJU=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_watch_architecture_enabled",
|
||||
"play_tab_enabled",
|
||||
"play_button_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7fUzmQDXkIOAnPhN+TpMm8Yky+5rlcKkKIs+Sp8uHnI=": {
|
||||
"name": "7fUzmQDXkIOAnPhN+TpMm8Yky+5rlcKkKIs+Sp8uHnI=",
|
||||
"value": {
|
||||
"can_edit": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qXeq9Bah5nolwlw4ANblNXv8R4m3/UViycx+j97xg+I=": {
|
||||
"name": "qXeq9Bah5nolwlw4ANblNXv8R4m3/UViycx+j97xg+I=",
|
||||
"value": {
|
||||
"Enabled": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"CFH3uZyZqjBSciklaVhlIumtUQyTUqZLJGTyBsTHBpc=": {
|
||||
"name": "CFH3uZyZqjBSciklaVhlIumtUQyTUqZLJGTyBsTHBpc=",
|
||||
"value": {
|
||||
"is_new_rrplus_page": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_new_rrplus_page"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"6B0G+WunEw6nuaXVUIGvopQQULiOXBPVEMjh1EPPBUQ=": {
|
||||
"name": "6B0G+WunEw6nuaXVUIGvopQQULiOXBPVEMjh1EPPBUQ=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"FilterOutInvalidRecipients",
|
||||
"OrderRecipients"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1M9QMfAzgNOpzGwoBUGzinKzLlbL3QBjBjn/tOfYur0=": {
|
||||
"name": "1M9QMfAzgNOpzGwoBUGzinKzLlbL3QBjBjn/tOfYur0=",
|
||||
"value": {
|
||||
"enable_discoverability": false,
|
||||
"enable_party_private_instance": false,
|
||||
"Lifecycle.PSF.EnableAllChatVoiceCalls": false,
|
||||
"Tip:Visibility": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_party_private_instance"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"atEECJHpWD+slvGvm9SDXQSMXpkwkHsx4EsgYv66XdI=": {
|
||||
"name": "atEECJHpWD+slvGvm9SDXQSMXpkwkHsx4EsgYv66XdI=",
|
||||
"value": {
|
||||
"show_playaction_button": false,
|
||||
"default_instance_type": "",
|
||||
"show_morelikethis_button": false,
|
||||
"supported_carousels": [],
|
||||
"new_rooms_threshold_days": 30,
|
||||
"hide_cheers_for_new": true,
|
||||
"hide_cheers_count": true,
|
||||
"show_cheers_data_token": false,
|
||||
"show_new_data_token": true,
|
||||
"loop_shimmer_animation": false,
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"show_publish_state": true,
|
||||
"Creator.Room.EnableRoomTokens": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_morelikethis_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"HwRUogKlebK4Hl92IDPVvKOXJ0Oh/fkmWnOzbg+SQ4U=": {
|
||||
"name": "HwRUogKlebK4Hl92IDPVvKOXJ0Oh/fkmWnOzbg+SQ4U=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test #2",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_customize_HUD_on",
|
||||
"customize_icon_mirror"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ARF23+Bv7v47hx9wUjUsqRNZRcp1+TCDwglzvtSsBAQ=": {
|
||||
"name": "ARF23+Bv7v47hx9wUjUsqRNZRcp1+TCDwglzvtSsBAQ=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured.NewItems.Control",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1OVuW8zIVdLRI8ob/ejjBdr3TAXT7a/3go9RCKtDW/M=": {
|
||||
"name": "1OVuW8zIVdLRI8ob/ejjBdr3TAXT7a/3go9RCKtDW/M=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "defaulted_to_tops",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"default_customize_tab_name"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"AFY/PGUcy3rJ4mc5nvIV0Rven0TX/axpm/VhqgJy3qY=": {
|
||||
"name": "AFY/PGUcy3rJ4mc5nvIV0Rven0TX/axpm/VhqgJy3qY=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server",
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BFRC3h/zIlll/HfJdkUotz4g3eeixt0Dd+m1ix4nNks=": {
|
||||
"name": "BFRC3h/zIlll/HfJdkUotz4g3eeixt0Dd+m1ix4nNks=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreClothing"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"pX1wycha2l9lY7Sv9qgm/Sbim55SajQpjOdmHWHzAus=": {
|
||||
"name": "pX1wycha2l9lY7Sv9qgm/Sbim55SajQpjOdmHWHzAus=",
|
||||
"value": {
|
||||
"third_person_feature_set": "FULL",
|
||||
"persistence": true,
|
||||
"Tip:Visibility": 0,
|
||||
"group_dance_with_gestures": true,
|
||||
"reduce_dance_options": false,
|
||||
"default_to_third_person": false,
|
||||
"toggle_in_orientation": true,
|
||||
"pinch_to_zoom": true,
|
||||
"tutorial_in_orientation": true,
|
||||
"new_melee": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_melee"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"kZR5q9i8kYU9Ky3SpnKfDvkMp4fTwSnCZZ4PsGFLQfA=": {
|
||||
"name": "kZR5q9i8kYU9Ky3SpnKfDvkMp4fTwSnCZZ4PsGFLQfA=",
|
||||
"value": {
|
||||
"use_app_nav": false,
|
||||
"prevent_watch_home": false,
|
||||
"CuratedList_AppNavWidgetPanel": "Discovery.PageSource.AppNavWidgetPanel",
|
||||
"use_app_nav_forship": false,
|
||||
"enable_app_nav_tutorial": false,
|
||||
"enable_retain_ui_state": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_app_nav_forship",
|
||||
"CuratedList_AppNavWidgetPanel",
|
||||
"enable_app_nav_tutorial"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"/niY+Ox2RFEo16LTqvLe87G+sPkVofeorVZzRQSoWp8=": {
|
||||
"name": "/niY+Ox2RFEo16LTqvLe87G+sPkVofeorVZzRQSoWp8=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 0,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"orientation_version",
|
||||
"target_orientation_room_name"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tTAuLmRojd31RIURFo4Zd7bELRoIDfPcW36vrivBj2Q=": {
|
||||
"name": "tTAuLmRojd31RIURFo4Zd7bELRoIDfPcW36vrivBj2Q=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_data_bound_store_item_card",
|
||||
"use_carousel_spawners"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"y9O+Iq9mDEVA56M7/PaK0ObgU1PE7HzJ/igajFMlkFY=": {
|
||||
"name": "y9O+Iq9mDEVA56M7/PaK0ObgU1PE7HzJ/igajFMlkFY=",
|
||||
"value": {
|
||||
"new_out_of_token_ui": true,
|
||||
"OutOfTokenSkuIds": [
|
||||
2,
|
||||
5,
|
||||
8,
|
||||
9,
|
||||
160,
|
||||
177,
|
||||
178,
|
||||
179,
|
||||
180
|
||||
],
|
||||
"ShowNewOutOfTokenPaths": true,
|
||||
"PopUpShopUsesRRUIFlow": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New UI and Top Up SKUs",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_out_of_token_ui",
|
||||
"OutOfTokenSkuIds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ZUnkW1jBr3GmTeZd1zhHvr5wVBvzZfMZkOdmHKanVac=": {
|
||||
"name": "ZUnkW1jBr3GmTeZd1zhHvr5wVBvzZfMZkOdmHKanVac=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"mJxTTJ5KMU2Tmjdflyr1DzKJJi8Z3DEiFjoFjQjGIQ4=": {
|
||||
"name": "mJxTTJ5KMU2Tmjdflyr1DzKJJi8Z3DEiFjoFjQjGIQ4=",
|
||||
"value": {
|
||||
"CuratedList_AppNav.WidgetPanel.Profile": "Discovery.PageSource.WWPanel.Profile",
|
||||
"CuratedList_WWPanel.Profile": "Discovery.PageSource.WWPanel.Profile"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_AppNav.WidgetPanel.Profile"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"HDU9zd3zphJnwQBMwVhEa7B9PJ0z9vraKhAqeyquQPk=": {
|
||||
"name": "HDU9zd3zphJnwQBMwVhEa7B9PJ0z9vraKhAqeyquQPk=",
|
||||
"value": {
|
||||
"scalability_enabled": true,
|
||||
"scalability_preset": -1,
|
||||
"scalability_low_power": false,
|
||||
"scalability_scalemode": 4,
|
||||
"scalability_avatarenable": true,
|
||||
"scalability_textureenable": false,
|
||||
"scalability_shapesenable": true,
|
||||
"scalability_target_num_batches": 0,
|
||||
"scalability_max_triangles": 2000,
|
||||
"scalability_shapes_mem_delta": 400,
|
||||
"scalability_r1_allowed": -1,
|
||||
"scalability_r2_allowed": -1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Dynamic Update Passive",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"scalability_enabled",
|
||||
"scalability_scalemode",
|
||||
"scalability_preset"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"TRCu71UaUTrArXnPdw27bPTcgmVTP6PUy7fmKxpM3EY=": {
|
||||
"name": "TRCu71UaUTrArXnPdw27bPTcgmVTP6PUy7fmKxpM3EY=",
|
||||
"value": {
|
||||
"TokenGiftDrop:Common": 3397,
|
||||
"TokenGiftDrop:Common:Subscribers": 4013,
|
||||
"TokenGiftDrop:Uncommon": 3396,
|
||||
"TokenGiftDrop:Uncommon:Subscribers": 4014,
|
||||
"TokenGiftDrop:Rare": 3395,
|
||||
"TokenGiftDrop:Rare:Subscribers": 4015,
|
||||
"TokenGiftDrop:Epic": 3394,
|
||||
"TokenGiftDrop:Epic:Subscribers": 4016
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qbYDDQZF1a3WpZw7sO0BnCbbxtjNnUN9elXoLO29gJk=": {
|
||||
"name": "qbYDDQZF1a3WpZw7sO0BnCbbxtjNnUN9elXoLO29gJk=",
|
||||
"value": {
|
||||
"use_unified_room_store": true,
|
||||
"show_all_category_room_store": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_unified_room_store",
|
||||
"show_all_category_room_store"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"0wU1z0Cx4ttZVhqOtdO+uAhH8vRREyuDFmZGBcGJiQM=": {
|
||||
"name": "0wU1z0Cx4ttZVhqOtdO+uAhH8vRREyuDFmZGBcGJiQM=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"dBWYF/mhetZ+B74cpeHJxd70Mz7A+x24DeJSA3nh2CE=": {
|
||||
"name": "dBWYF/mhetZ+B74cpeHJxd70Mz7A+x24DeJSA3nh2CE=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"VDjnf4Aeq/nnvC3Q8UlaEDEthBFqJbPt+OsanrMO950=": {
|
||||
"name": "VDjnf4Aeq/nnvC3Q8UlaEDEthBFqJbPt+OsanrMO950=",
|
||||
"value": {
|
||||
"show_share_in_chat_button": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"SUgIRdYXbT8UNUaXdNlk/agkydMq+pWs0bEPjnzhpEg=": {
|
||||
"name": "SUgIRdYXbT8UNUaXdNlk/agkydMq+pWs0bEPjnzhpEg=",
|
||||
"value": {
|
||||
"layout_variant": "E"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "TestE",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1FKE7cZBsRa50U+vI7qFds/pGHnZAeZZoxhoNx1IhaM=": {
|
||||
"name": "1FKE7cZBsRa50U+vI7qFds/pGHnZAeZZoxhoNx1IhaM=",
|
||||
"value": {
|
||||
"loc_enabled": true,
|
||||
"GroupName": "Autoloc",
|
||||
"autoloc_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Autoloc",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"loc_enabled",
|
||||
"GroupName",
|
||||
"autoloc_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"LU1K9/rnuhftm486UqhARNv4zQT7E5mWYi0o9b6d7oQ=": {
|
||||
"name": "LU1K9/rnuhftm486UqhARNv4zQT7E5mWYi0o9b6d7oQ=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"PlayMenuCarouselOrdering_1",
|
||||
"PlayMenuCuratedCarouselsMap",
|
||||
"GroupName"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"3/iLq3/DXwgsbIozuJ1r3MT31dp+HO6qYg34fmzKhRY=": {
|
||||
"name": "3/iLq3/DXwgsbIozuJ1r3MT31dp+HO6qYg34fmzKhRY=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 3500,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ios_highend_min_physical_memory"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"mU76r2CqbyxIcRmgpjC4vFLX9ZFV7kx0AVSfV7baDgw=": {
|
||||
"name": "mU76r2CqbyxIcRmgpjC4vFLX9ZFV7kx0AVSfV7baDgw=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hideCancelPurchaseDialog",
|
||||
"skipBundleDetailsPage",
|
||||
"hidePlatformSKUConfirmationDialogs",
|
||||
"showHorizontalScrollTokenStore",
|
||||
"card_size"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"cvtLJ4c5/nnh2Q+eKTtsylvxPJs0LCQt5a+mZXbRD0I=": {
|
||||
"name": "cvtLJ4c5/nnh2Q+eKTtsylvxPJs0LCQt5a+mZXbRD0I=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseFourBoneSkinnedMeshRenderers"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"IPQpNjw48nuWgkMZGvherVZdEydOrViq2K+3fjE9BFU=": {
|
||||
"name": "IPQpNjw48nuWgkMZGvherVZdEydOrViq2K+3fjE9BFU=",
|
||||
"value": {
|
||||
"IsCustomDormForNewUsersEnabled": false,
|
||||
"IsCustomDormCustomizationLocked": false,
|
||||
"ShowNewTutorial": false,
|
||||
"ShowTutorialOnSpawn": false,
|
||||
"IsDormSlotsVisible": true,
|
||||
"ShowDiscoveryNotification": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "DormSlotsVisible",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"IsDormSlotsVisible"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"E0mZbhCx9TgRfotBjky+RCmpmvQVGoJ/OSW+qJpO+IE=": {
|
||||
"name": "E0mZbhCx9TgRfotBjky+RCmpmvQVGoJ/OSW+qJpO+IE=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_mixed_carousel_for_store_categories",
|
||||
"use_categorization_improvements_store_v2"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Nqx3oIktD5GIAHU0csNOCbfavgQrBQVsdrKtT5H4gSw=": {
|
||||
"name": "Nqx3oIktD5GIAHU0csNOCbfavgQrBQVsdrKtT5H4gSw=",
|
||||
"value": {
|
||||
"show_ccu_count": false,
|
||||
"show_popular_badge": true,
|
||||
"pin_popular_room": false,
|
||||
"supported_carousels": [
|
||||
"SEARCH_ROOMS_WATCH"
|
||||
],
|
||||
"Discovery.CCUIconThresholds": "[ { \"Count\": 50, \"Label\": \"Hot\", \"Color\": \"#EA6F48\" } ]",
|
||||
"Discovery.PopularBadgeCCUThreshold": 50,
|
||||
"best_popular_card_threshold": -1,
|
||||
"room_list_join_or_create_count": 0,
|
||||
"room_list_join_only_count": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"best_popular_card_threshold",
|
||||
"room_list_join_or_create_count",
|
||||
"room_list_join_only_count"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fSgJPzzw86461oPdsMygmBirmUTXNKeBSG/2WIpoH1Q=": {
|
||||
"name": "fSgJPzzw86461oPdsMygmBirmUTXNKeBSG/2WIpoH1Q=",
|
||||
"value": {
|
||||
"MaxRoomCount": -999,
|
||||
"Discovery.GenreBasedMatchmaking.FreshnessParameter": 5,
|
||||
"JoinOrCreateRoomCount": 10,
|
||||
"AdditionalJoinOnlyRoomCount": 15
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "SomeJoinOnly",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"JoinOrCreateRoomCount",
|
||||
"AdditionalJoinOnlyRoomCount"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"72NOZV1epsB4qZYKmYU8KlaobkKgj+J/bNrq7aZcMj4=": {
|
||||
"name": "72NOZV1epsB4qZYKmYU8KlaobkKgj+J/bNrq7aZcMj4=",
|
||||
"value": {
|
||||
"Retention.Coordinate.BeaconMVPEnabled": false,
|
||||
"Retention.Coordinate.AllowVRPlayerBeaconControls": false,
|
||||
"Retention.Coordinate.UseWiderVRLaserWhenBeaconsEnabled": false
|
||||
},
|
||||
"rule_id": "targetingGate",
|
||||
"group": "targetingGate",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Retention.Coordinate.BeaconMVPEnabled",
|
||||
"Retention.Coordinate.AllowVRPlayerBeaconControls",
|
||||
"Retention.Coordinate.UseWiderVRLaserWhenBeaconsEnabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "706125647",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dXIh+60eraHhil8IXB1ZdGo7AUb+3Vajw+x3NaGcICI=": {
|
||||
"name": "dXIh+60eraHhil8IXB1ZdGo7AUb+3Vajw+x3NaGcICI=",
|
||||
"value": {
|
||||
"IsSearchWithTagsEnabled": true,
|
||||
"UseElasticsearch": true,
|
||||
"UseSecondaryOrdering": true,
|
||||
"UseFuzzySearch": true,
|
||||
"SearchDescription": false,
|
||||
"NameRelevanceBoost": 1,
|
||||
"TagRelevanceBoost": 1,
|
||||
"DescriptionRelevanceBoost": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Use 2ndary ordering",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseSecondaryOrdering"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xJW4rRRsKYTGTmwMvTgVZaToEZknVlMjLl50uveb640=": {
|
||||
"name": "xJW4rRRsKYTGTmwMvTgVZaToEZknVlMjLl50uveb640=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tryon",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": false,
|
||||
"ShowTokenStore": false,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": false,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowStoreBadgeNotification"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"EZq2LJw/m0cMuNF/p3Nm5gIn0H3E6sTEro7Dg7alBP0=": {
|
||||
"name": "EZq2LJw/m0cMuNF/p3Nm5gIn0H3E6sTEro7Dg7alBP0=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": false,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"PurchaseReminderPrefabVariantOverride",
|
||||
"UseBackgroundImageForPurchaseReminderOverride",
|
||||
"Override_Content_Json"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"LdvPeF4D7tTedPeZyjvVceSbDPx9+E5sjfGBvyzXicw=": {
|
||||
"name": "LdvPeF4D7tTedPeZyjvVceSbDPx9+E5sjfGBvyzXicw=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"VBhTCb5s/B9bIsBrBpa41ejJyYmSvwisfJd+S0BB380=": {
|
||||
"name": "VBhTCb5s/B9bIsBrBpa41ejJyYmSvwisfJd+S0BB380=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"target_orientation_room_name",
|
||||
"orientation_version",
|
||||
"orientation_sub_room_names"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"pXuyHDFFJBmPbZhS15UD3QlkGW5G/9TfHSotvGcP/hI=": {
|
||||
"name": "pXuyHDFFJBmPbZhS15UD3QlkGW5G/9TfHSotvGcP/hI=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"k65l2XxEzrbcHVIQsg46LKDiiJ6DcWmEs37431HInjE=": {
|
||||
"name": "k65l2XxEzrbcHVIQsg46LKDiiJ6DcWmEs37431HInjE=",
|
||||
"value": {
|
||||
"useOptimizedSchedulerUpdateQueues": false,
|
||||
"optimizeRbexSleep": false,
|
||||
"optimizeSyncParent": true,
|
||||
"slowdown_duration_ms": 0,
|
||||
"hitch_interval_ms": 0,
|
||||
"hitch_duration_ms": 0,
|
||||
"targetFramerate": 0,
|
||||
"maintain_fps": 0,
|
||||
"smallUpdateOptimizations2": true,
|
||||
"cullOldNetworkTransformSyncData": true,
|
||||
"opt_android_reliable_ticks": true,
|
||||
"optimizeQuestUmbraCulling": true,
|
||||
"optimizeDecals": true,
|
||||
"srp_batch_support_decals": true,
|
||||
"srp_batch_support_makerpen": true,
|
||||
"srp_batch_support_avatars": true,
|
||||
"srp_batch_healthbars": true,
|
||||
"optimizeFaceCameraUIAnchorSync": true,
|
||||
"useCachedViewPostprocessingOptimizations": true,
|
||||
"enableRRStudioCanvasCulling": true,
|
||||
"optimizeVoiceLinesController2D": true,
|
||||
"optimizeDistanceFadeManager": true,
|
||||
"enableCanvasDistanceCulling": true,
|
||||
"optimizeRbexSleepKinematicCheck": true,
|
||||
"optimizeSFXAndParticleReleaseMonitoring": true,
|
||||
"max_fixedUpdates_onMobile": 0,
|
||||
"optimizeTerrain": true,
|
||||
"optimizePhysicalTextUpdate": true,
|
||||
"optimizeConnectedGrabbableDecorationColliders": false,
|
||||
"optimize_fastgraphicraycaster_visibility": true,
|
||||
"disableUIAnchorFaceCameraForMirrors": true,
|
||||
"cullRemotePlayerMovementFeedbackParticles": true,
|
||||
"optimizeScreenInputHandlerUpdateStateForVR": true,
|
||||
"enableMuzzleFlashDistanceCulling": true,
|
||||
"fixAudioDistanceChecks": false,
|
||||
"optimize_avatar_network_interpolation": true,
|
||||
"useMirrorOcclusionCulling": true,
|
||||
"useNametagNonUGUI": true,
|
||||
"forceIOSHitching": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"useOptimizedSchedulerUpdateQueues"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"98K2mqq5F+a3bLlU+XO0wo87rjvRQgBsYWN+Jmok0aI=": {
|
||||
"name": "98K2mqq5F+a3bLlU+XO0wo87rjvRQgBsYWN+Jmok0aI=",
|
||||
"value": {
|
||||
"StartTime": "2030-01-01T00:00:00Z",
|
||||
"EndTime": "2030-01-02T00:00:00Z",
|
||||
"TLA.Config": "{}",
|
||||
"TLA.Enabled": "false",
|
||||
"IsEnabled": false,
|
||||
"LiveOps.TLA.UpcomingOrActiveId": "",
|
||||
"LiveOps.TLA.Displacement.Enabled": false,
|
||||
"LiveOps.TLA.Displacement.DPM": "true",
|
||||
"TeamRegistrationStartTime": "2030-01-01T00:00:00Z",
|
||||
"TeamRegistrationEndTime": "2030-01-02T00:00:00Z"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"LiveOps.TLA.UpcomingOrActiveId",
|
||||
"StartTime",
|
||||
"EndTime",
|
||||
"TeamRegistrationStartTime",
|
||||
"TeamRegistrationEndTime"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"S0CyAObemZJshg+nPw+0rjgKujdmyxzp0sK863H9eT0=": {
|
||||
"name": "S0CyAObemZJshg+nPw+0rjgKujdmyxzp0sK863H9eT0=",
|
||||
"value": {
|
||||
"plus_button_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"EJo4NlRdfMxZ8RFm806aX36rCuLoZCLW5liX7agirGs=": {
|
||||
"name": "EJo4NlRdfMxZ8RFm806aX36rCuLoZCLW5liX7agirGs=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"exclude_owned"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"r2jaEtr7P95Z+fTYo80nUAUdAEcT77f1sJ1lbHUcxJk=": {
|
||||
"name": "r2jaEtr7P95Z+fTYo80nUAUdAEcT77f1sJ1lbHUcxJk=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"RemoveOwnedItemsFromWishlist"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"osVl17TcZ2dGUuMtho8toaqHHG8KVhAlxFoa1EkhHw0=": {
|
||||
"name": "osVl17TcZ2dGUuMtho8toaqHHG8KVhAlxFoa1EkhHw0=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowGiftingBadgesOnItemDetails",
|
||||
"ShowGiftingBadgesOnStoreItemCard"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hHaksCA5sjTfyU6POIyUMisGj3KKUMjK61B9Fo/YMpk=": {
|
||||
"name": "hHaksCA5sjTfyU6POIyUMisGj3KKUMjK61B9Fo/YMpk=",
|
||||
"value": {
|
||||
"should_lazy_load_fallback_fonts": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"162mCp2+SGBevhUWuZKu2pOyCdYopQ/RG7nvwv752G4=": {
|
||||
"name": "162mCp2+SGBevhUWuZKu2pOyCdYopQ/RG7nvwv752G4=",
|
||||
"value": {
|
||||
"apply_laser_teleporter_memory_optimization": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1AFY/v2/pwrOC/fZ3MBwq1zQfZREG3+ZRolKt5A3FBc=": {
|
||||
"name": "1AFY/v2/pwrOC/fZ3MBwq1zQfZREG3+ZRolKt5A3FBc=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Store Button",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"store_tab_enabled",
|
||||
"expand_vr_nav_bar"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9B6mXcXTVG8LPicevUs5ZQy+kU4QULJ5rtXpvmys2wc=": {
|
||||
"name": "9B6mXcXTVG8LPicevUs5ZQy+kU4QULJ5rtXpvmys2wc=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"store_item_prefab_variant_name",
|
||||
"hide_rarity",
|
||||
"use_buy_cta_button",
|
||||
"hide_prediscount_price"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"8pK+XlQF1+EpGpwjrlnfG8v+oeenO3YumnrKUh2Z64s=": {
|
||||
"name": "8pK+XlQF1+EpGpwjrlnfG8v+oeenO3YumnrKUh2Z64s=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": true,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": false,
|
||||
"options_count_per_platform": {},
|
||||
"AutoCompleteIndexName": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_autocomplete",
|
||||
"options_count_per_platform",
|
||||
"AutoCompleteIndexName"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QHAIHIzO4UcnPE2LrBwJ9kNjT+9h3AsjVOoi0enuOFw=": {
|
||||
"name": "QHAIHIzO4UcnPE2LrBwJ9kNjT+9h3AsjVOoi0enuOFw=",
|
||||
"value": {
|
||||
"clusterlod_enable_vertex_compression": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Vertex Compression Enabled",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"jwscIHsylCD8/Qd3PHSABky06KHHhn7YnVWAexwzKVg=": {
|
||||
"name": "jwscIHsylCD8/Qd3PHSABky06KHHhn7YnVWAexwzKVg=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight.Events.Control",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"laFZymxsEdCpDvE45glpQ8/NVaiyJyIN0rjzflb7QVU=": {
|
||||
"name": "laFZymxsEdCpDvE45glpQ8/NVaiyJyIN0rjzflb7QVU=",
|
||||
"value": {
|
||||
"should_autofocus_search": true,
|
||||
"should_prevent_autofocus_if_no_hardware_keyboard": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test - Autofocus If Hardware Keyboard",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"2RJq04GrTUNTTN3T++CLPc/tgryUXQuEHa0tqRaEG14=": {
|
||||
"name": "2RJq04GrTUNTTN3T++CLPc/tgryUXQuEHa0tqRaEG14=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"gOsZ5uPqhrpqtxcVzHwm5+PU008AWAcUYKyyD5HhX0U=": {
|
||||
"name": "gOsZ5uPqhrpqtxcVzHwm5+PU008AWAcUYKyyD5HhX0U=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Classic Bean Disabled",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseClassicBeanOverrideEnabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FopgzvKDRTC06xSbAXfYJZ+KZS7umdCklasFZ9KCHjg=": {
|
||||
"name": "FopgzvKDRTC06xSbAXfYJZ+KZS7umdCklasFZ9KCHjg=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"force_no_horizontal_scroll"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"l3OgtBvo8Ya6HttNkuDiftFw6oHFuMMUs60aqPyVXeg=": {
|
||||
"name": "l3OgtBvo8Ya6HttNkuDiftFw6oHFuMMUs60aqPyVXeg=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_unified_avatar_item_search",
|
||||
"UGC_search_text_title",
|
||||
"RRO_items_search_title",
|
||||
"use_new_filter_ordering",
|
||||
"CAI_search_includes_coach_items",
|
||||
"extra_purchasable_item_ids_in_default_search",
|
||||
"cai_default_ordering"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"eVFQJXqwCih7iJMIKD32MSnkto5Ziqo5yzv9bYl4cCc=": {
|
||||
"name": "eVFQJXqwCih7iJMIKD32MSnkto5Ziqo5yzv9bYl4cCc=",
|
||||
"value": {
|
||||
"save_cached_view_mem": true,
|
||||
"ios_low_max_mirror_mp": 1,
|
||||
"enabled": false,
|
||||
"ios_low_max_mp": 1,
|
||||
"memoryless_aux_surfaces_sharecam": true,
|
||||
"memoryless_aux_surfaces_cachedviews": true,
|
||||
"memoryless_aux_surfaces_pip": true,
|
||||
"memoryless_aux_surfaces_sniper_scope": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"memoryless_aux_surfaces_sharecam",
|
||||
"memoryless_aux_surfaces_pip",
|
||||
"memoryless_aux_surfaces_cachedviews",
|
||||
"memoryless_aux_surfaces_sniper_scope"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"/zbaFKA6JQBKimyM1GKhMvdZ06UmnwpQvuA60EgerfA=": {
|
||||
"name": "/zbaFKA6JQBKimyM1GKhMvdZ06UmnwpQvuA60EgerfA=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0vtpZSzKVJMK+N3AR5YO+lT7BPUW/LttmOSHwuURG5g=": {
|
||||
"name": "0vtpZSzKVJMK+N3AR5YO+lT7BPUW/LttmOSHwuURG5g=",
|
||||
"value": {
|
||||
"use_app_nav": false,
|
||||
"prevent_watch_home": false,
|
||||
"CuratedList_AppNavWidgetPanel": "Discovery.PageSource.AppNavWidgetPanel",
|
||||
"use_app_nav_forship": false,
|
||||
"enable_app_nav_tutorial": false,
|
||||
"enable_retain_ui_state": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_app_nav_forship"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+NV5gpWS4lK/G4oISHkpSwiQn95hSAfWgEEF0kSIBZY=": {
|
||||
"name": "+NV5gpWS4lK/G4oISHkpSwiQn95hSAfWgEEF0kSIBZY=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"KZe40SBqcGSGVenGR+GVvVAeEu+5YiXYMqLDBRoTNSI=": {
|
||||
"name": "KZe40SBqcGSGVenGR+GVvVAeEu+5YiXYMqLDBRoTNSI=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tryon",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": false,
|
||||
"ShowTokenStore": false,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": true,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_shopping_bag_on_add"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Tk3u905YLnwZKoGSY2yt6EWmEB/6bkqCwlnev5mjUDQ=": {
|
||||
"name": "Tk3u905YLnwZKoGSY2yt6EWmEB/6bkqCwlnev5mjUDQ=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"autospawn_energy_threshold"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"2ocI/uNhczVmU1DhpoEL7MiCVS0MVS7sfB+hMVlf7IQ=": {
|
||||
"name": "2ocI/uNhczVmU1DhpoEL7MiCVS0MVS7sfB+hMVlf7IQ=",
|
||||
"value": {
|
||||
"boot_sequence_implementation_version": 31
|
||||
},
|
||||
"rule_id": "targetingGate",
|
||||
"group": "targetingGate",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"boot_sequence_implementation_version"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3568105362",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"O9pSJxgM19PkuMuSPC3az7jtbX49AaUxMY2UKoDQAeE=": {
|
||||
"name": "O9pSJxgM19PkuMuSPC3az7jtbX49AaUxMY2UKoDQAeE=",
|
||||
"value": {
|
||||
"rrplus_paywall_type": "prevent_confirming_creation",
|
||||
"number_of_saved_outfits": 100,
|
||||
"show_mirror_button": true,
|
||||
"show_top_level_tab": true,
|
||||
"show_customizer_card": true,
|
||||
"show_back_facing_custom_shirt_preview": false,
|
||||
"show_new_customizer_buttons": true,
|
||||
"show_undo_redo_buttons": true,
|
||||
"default_custom_shirt_price": 1000
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Paywall at Confirming",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"rrplus_paywall_type"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"U9Gy6GkQymVTVkBCbtUqZLzBwoCJUETmEE8YRHM0kiM=": {
|
||||
"name": "U9Gy6GkQymVTVkBCbtUqZLzBwoCJUETmEE8YRHM0kiM=",
|
||||
"value": {
|
||||
"is_rrui": false,
|
||||
"slot_count_non_rrplus": 18
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"mJXsNCjr8P+ehxv71IcVdl1FVPpORkabRlw2kO21uAo=": {
|
||||
"name": "mJXsNCjr8P+ehxv71IcVdl1FVPpORkabRlw2kO21uAo=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "NewPeopleCardJoin",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"rrui_people_menu_enabled",
|
||||
"use_new_people_card",
|
||||
"use_new_people_card_invite_variant",
|
||||
"hide_large_people_search_bar",
|
||||
"friends_default_tab",
|
||||
"friends_subtab"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"R6JcvzF8iNXJOo9YZqptiYh1LAoPjAaoJm3Z4fSMhaI=": {
|
||||
"name": "R6JcvzF8iNXJOo9YZqptiYh1LAoPjAaoJm3Z4fSMhaI=",
|
||||
"value": {
|
||||
"card_size": "large",
|
||||
"hidden_types": [],
|
||||
"display_order": [],
|
||||
"display order": {},
|
||||
"isPromptingLowestTokenPackage": false,
|
||||
"prompt_lowest_token_package": false,
|
||||
"only_prompt_non_payer": true,
|
||||
"hide_token_store": false,
|
||||
"hide_buy_token_and_items": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"card_size",
|
||||
"hidden_types",
|
||||
"display_order",
|
||||
"prompt_lowest_token_package",
|
||||
"only_prompt_non_payer"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ms7eGt+DqrIhb0HZbvvV3l7LCJokFyRkQnP6cMwueIM=": {
|
||||
"name": "ms7eGt+DqrIhb0HZbvvV3l7LCJokFyRkQnP6cMwueIM=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"kmEB5nkTNyK3A2bvXcut98xHh1BKa8cCkaqhWjkN0Cs=": {
|
||||
"name": "kmEB5nkTNyK3A2bvXcut98xHh1BKa8cCkaqhWjkN0Cs=",
|
||||
"value": {
|
||||
"use_custom_backpack_icon": false,
|
||||
"show_games_tab": true,
|
||||
"Tip:Visibility": 0,
|
||||
"DataCollection.LogFriendIdsWithSocialHeartbeat": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Game Only",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_custom_backpack_icon",
|
||||
"show_games_tab",
|
||||
"Tip:Visibility"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"LWs5t1Wg2k2BmtK+sLqEJzQKEdU9J3buYMSHebp1Igc=": {
|
||||
"name": "LWs5t1Wg2k2BmtK+sLqEJzQKEdU9J3buYMSHebp1Igc=",
|
||||
"value": {
|
||||
"show_reactions": true,
|
||||
"show_emotes": true,
|
||||
"reactions": [
|
||||
"👍",
|
||||
"❤️",
|
||||
"🎉",
|
||||
"😭:Sad",
|
||||
"👎",
|
||||
"😲:Shocked",
|
||||
"💀",
|
||||
"🤣:Laugh"
|
||||
],
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [],
|
||||
"show_react_gesture_tip": false,
|
||||
"fade_react_joystick": false,
|
||||
"show_react_joystick": false,
|
||||
"react_gesture_tip": "",
|
||||
"show_react_radial_joystick": false,
|
||||
"custom_local_profile": true,
|
||||
"show_profile_blocklist": false,
|
||||
"hide_old_set_badges": true,
|
||||
"Tip:Visibility": 1,
|
||||
"snowball_enabled": false,
|
||||
"snowball_feedback_sizing": false,
|
||||
"snowball_feedback_vfx": false,
|
||||
"snowball_feedback_room_chat": false,
|
||||
"snowball_feedback_notification": false
|
||||
},
|
||||
"rule_id": "6QgSAFFJwBxslkhuBZzOzu:override",
|
||||
"group": "6QgSAFFJwBxslkhuBZzOzu:override",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"custom_local_profile",
|
||||
"show_profile_blocklist",
|
||||
"hide_old_set_badges",
|
||||
"show_reactions",
|
||||
"show_emotes",
|
||||
"reactions",
|
||||
"Tip:Visibility"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"OrBOJNLxfuXIM++mTxldtSHRWwMtRz5b3302A1vg5sA=": {
|
||||
"name": "OrBOJNLxfuXIM++mTxldtSHRWwMtRz5b3302A1vg5sA=",
|
||||
"value": {
|
||||
"Retention.Coordinate.BeaconMVPEnabled": true,
|
||||
"Retention.Coordinate.HintTutorialEnabled": false,
|
||||
"Retention.Coordinate.HeadsUpTutorialEnabled": true
|
||||
},
|
||||
"rule_id": "7xPuIlW2MzkBVJMdPlmFeW:override",
|
||||
"group": "7xPuIlW2MzkBVJMdPlmFeW:override",
|
||||
"group_name": "Heads up",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Retention.Coordinate.BeaconMVPEnabled",
|
||||
"Retention.Coordinate.HintTutorialEnabled",
|
||||
"Retention.Coordinate.HeadsUpTutorialEnabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"R4HEqtg3zbnqNGOoyiWkSlEmf6BMV+rSgQ1laOv4T/4=": {
|
||||
"name": "R4HEqtg3zbnqNGOoyiWkSlEmf6BMV+rSgQ1laOv4T/4=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0gcuCQdTm+lAovjmb5yL2y8TRi47m/uY7Kb4HFvS8Lg=": {
|
||||
"name": "0gcuCQdTm+lAovjmb5yL2y8TRi47m/uY7Kb4HFvS8Lg=",
|
||||
"value": {
|
||||
"link_to_custom_endpoint": true,
|
||||
"endpoint_name": "itemsforyou",
|
||||
"page_title": "Recommended For You",
|
||||
"entity_type": "",
|
||||
"source": "AlgorithmicList"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"link_to_custom_endpoint",
|
||||
"endpoint_name",
|
||||
"page_title",
|
||||
"source"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GK7MdTboDQABxI7Rt2NZIusz/rpkAyUrE/YsopHwYWU=": {
|
||||
"name": "GK7MdTboDQABxI7Rt2NZIusz/rpkAyUrE/YsopHwYWU=",
|
||||
"value": {
|
||||
"enable_retain_ui_state": true,
|
||||
"show_watch_button_in_menus": false,
|
||||
"portal_buttons_act_like_tabs": false,
|
||||
"portal_always_present": false,
|
||||
"enable_retain_cursor_position": true,
|
||||
"retain_ui_state_duration": 60,
|
||||
"use_dir_nav": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"portal_always_present",
|
||||
"portal_buttons_act_like_tabs",
|
||||
"show_watch_button_in_menus"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QHnaOIqd6pdwYPTy49FHSUZeAt/2EIplSoj30HagCsE=": {
|
||||
"name": "QHnaOIqd6pdwYPTy49FHSUZeAt/2EIplSoj30HagCsE=",
|
||||
"value": {
|
||||
"CuratedList_AppNavPortalPanel": "Discovery.PageSource.AppNavPortalPanel"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_AppNavPortalPanel"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Dje3wzF2bnUMfDCeMkgs+n6YKmJavTJyTd1RcQCcrFI=": {
|
||||
"name": "Dje3wzF2bnUMfDCeMkgs+n6YKmJavTJyTd1RcQCcrFI=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "3721940192",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vc+zmB7DH9J6EXoDlksxvwi1OFxzAfXV836kwr9japM=": {
|
||||
"name": "vc+zmB7DH9J6EXoDlksxvwi1OFxzAfXV836kwr9japM=",
|
||||
"value": {
|
||||
"s2t_enabled": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"fwmnKq3tFepJMdSca4VXQOlWJRBBa8fwmZr5JEq7cn4=": {
|
||||
"name": "fwmnKq3tFepJMdSca4VXQOlWJRBBa8fwmZr5JEq7cn4=",
|
||||
"value": {
|
||||
"TIMEOUT_PERIOD_IN_SECONDS": 60,
|
||||
"NUMBER_OF_MESSAGES_BEFORE_LIMITING": 3,
|
||||
"LIMITING_ENABLED": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"+VcygzxvEYRJK9+gH6GBW1OVfsddLxTd+wdATtcDyRI=": {
|
||||
"name": "+VcygzxvEYRJK9+gH6GBW1OVfsddLxTd+wdATtcDyRI=",
|
||||
"value": {
|
||||
"GroupName": "Control",
|
||||
"workflow_variant": "C"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"GroupName",
|
||||
"workflow_variant"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "2896719161",
|
||||
"gateValue": "true",
|
||||
"ruleID": "2BLgDcVEq1xkvoMaPdnGU5"
|
||||
}
|
||||
]
|
||||
},
|
||||
"7J+XirHl3GUNJ73XInfuNLeEfbw8qLEIzMrzJjAmBBc=": {
|
||||
"name": "7J+XirHl3GUNJ73XInfuNLeEfbw8qLEIzMrzJjAmBBc=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Remove Horizontal Scroll",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"remove_horizontal_scroll",
|
||||
"remove_chips",
|
||||
"remove_metadata_from_cards"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ch+67AIRqrZL6S/slccOmVlPtdD5JCYidoCBkBB2ZuU=": {
|
||||
"name": "Ch+67AIRqrZL6S/slccOmVlPtdD5JCYidoCBkBB2ZuU=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": false,
|
||||
"rrui_notifications_enabled": false,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_BestOfRecRoom",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Events_Live",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_watch_architecture_enabled",
|
||||
"rrui_notifications_enabled",
|
||||
"home_larger_main_menu_enabled",
|
||||
"home_play_button_alt_icon_enabled",
|
||||
"carousel_order"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"MLVdqisRg5TZjyDmx23ZXfgwkWulp7wnyIAxY1OeBoE=": {
|
||||
"name": "MLVdqisRg5TZjyDmx23ZXfgwkWulp7wnyIAxY1OeBoE=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"kLohpzbjAX7+8mzMW/3tabYclxqfym+XEhZebMzyoZ0=": {
|
||||
"name": "kLohpzbjAX7+8mzMW/3tabYclxqfym+XEhZebMzyoZ0=",
|
||||
"value": {
|
||||
"use_curated_list": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_curated_list"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zvBfegBDA5YjWaN1UA7Ks2whY5XeEtbiKf8rqInTiUg=": {
|
||||
"name": "zvBfegBDA5YjWaN1UA7Ks2whY5XeEtbiKf8rqInTiUg=",
|
||||
"value": {
|
||||
"show_rrui_for_leave_party": true,
|
||||
"show_room_capacity_party_size_warning": false,
|
||||
"show_detailed_account_cards": true,
|
||||
"show_separate_partychat_button": true,
|
||||
"show_party_buttons_on_top": true,
|
||||
"show_goto_in_partychat_details": true,
|
||||
"use_party_invite_message": false,
|
||||
"show_party_chat_in_room_chat_menu": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Variant 2",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_detailed_account_cards",
|
||||
"show_separate_partychat_button",
|
||||
"show_party_buttons_on_top"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"EQpdZmHP9UYzdtTxClMm/dFokL2v34iQG3kVra6CUQs=": {
|
||||
"name": "EQpdZmHP9UYzdtTxClMm/dFokL2v34iQG3kVra6CUQs=",
|
||||
"value": {
|
||||
"ShowThisRoomStore": true,
|
||||
"UseClosestToSpawn": false,
|
||||
"AllowStorefrontConfiguration": true,
|
||||
"ShowCTA": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Configured",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"VblcbPGMMQHshh1BVIkpYC8rQbux6GAk66BPsrUDmmY=": {
|
||||
"name": "VblcbPGMMQHshh1BVIkpYC8rQbux6GAk66BPsrUDmmY=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"x9vXkQslQSdONPmSbf4P4IiofTqtL760p/yD592IUzI=": {
|
||||
"name": "x9vXkQslQSdONPmSbf4P4IiofTqtL760p/yD592IUzI=",
|
||||
"value": {
|
||||
"load_maker_pen_on_demand": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tms79tGBbwFAtNZk/m+8ify/o+YR7GkPyLlt9KPSzFM=": {
|
||||
"name": "tms79tGBbwFAtNZk/m+8ify/o+YR7GkPyLlt9KPSzFM=",
|
||||
"value": {
|
||||
"show_reactions": true,
|
||||
"show_emotes": true,
|
||||
"reactions": [
|
||||
"👍",
|
||||
"❤️",
|
||||
"🎉",
|
||||
"😭:Sad",
|
||||
"👎",
|
||||
"😲:Shocked",
|
||||
"💀",
|
||||
"🤣:Laugh"
|
||||
],
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [],
|
||||
"show_react_gesture_tip": false,
|
||||
"fade_react_joystick": false,
|
||||
"show_react_joystick": false,
|
||||
"react_gesture_tip": "",
|
||||
"show_react_radial_joystick": false,
|
||||
"custom_local_profile": false,
|
||||
"show_profile_blocklist": false,
|
||||
"hide_old_set_badges": false,
|
||||
"Tip:Visibility": 0,
|
||||
"snowball_enabled": false,
|
||||
"snowball_feedback_sizing": false,
|
||||
"snowball_feedback_vfx": false,
|
||||
"snowball_feedback_room_chat": false,
|
||||
"snowball_feedback_notification": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"emote_hud_icon_override",
|
||||
"hud_element_priority",
|
||||
"fade_react_joystick",
|
||||
"show_react_joystick",
|
||||
"react_gesture_tip",
|
||||
"show_reactions",
|
||||
"show_emotes",
|
||||
"reactions"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"65pSNXQq0PY6hJxhwIwraYdghJebVf7zYf8iDr5oGU4=": {
|
||||
"name": "65pSNXQq0PY6hJxhwIwraYdghJebVf7zYf8iDr5oGU4=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured.PopUpShop_1Reorder.Control",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"c6/1KQZn5nPpvz/ICaLCgFk+LmvgbEz8ec1y+UnOc3g=": {
|
||||
"name": "c6/1KQZn5nPpvz/ICaLCgFk+LmvgbEz8ec1y+UnOc3g=",
|
||||
"value": {
|
||||
"isEnabled": false,
|
||||
"cooldown_base": 0,
|
||||
"cooldown_scale": 0,
|
||||
"cooldown_min": 0,
|
||||
"cooldown_max": 0,
|
||||
"cooldown_max_views": 0,
|
||||
"is_enabled_chat": false,
|
||||
"is_enabled_event": false,
|
||||
"is_enabled_gift": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"cooldown_base",
|
||||
"cooldown_scale",
|
||||
"cooldown_min",
|
||||
"cooldown_max",
|
||||
"cooldown_max_views",
|
||||
"is_enabled_gift"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2345755404",
|
||||
"gateValue": "false",
|
||||
"ruleID": "1RyXaibUwiHWUoNGlHTkMJ:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"XfnNEGEVRVt2eeGnaRV2aGQdBDZDDy8DXusDJl9GSTs=": {
|
||||
"name": "XfnNEGEVRVt2eeGnaRV2aGQdBDZDDy8DXusDJl9GSTs=",
|
||||
"value": {
|
||||
"optimize_expresso_ui": false,
|
||||
"optimize_raycasts": false,
|
||||
"optimize_nearest_canvas": false,
|
||||
"cache_uri_route_strings": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"optimize_nearest_canvas"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+ePSdtbtsGcPk1qur+m+2Mj6Q5PHT9Fe1+53g2MoJ8Y=": {
|
||||
"name": "+ePSdtbtsGcPk1qur+m+2Mj6Q5PHT9Fe1+53g2MoJ8Y=",
|
||||
"value": {
|
||||
"show_playaction_button": false,
|
||||
"default_instance_type": "",
|
||||
"show_morelikethis_button": false,
|
||||
"supported_carousels": [],
|
||||
"new_rooms_threshold_days": 30,
|
||||
"hide_cheers_for_new": true,
|
||||
"hide_cheers_count": true,
|
||||
"show_cheers_data_token": false,
|
||||
"show_new_data_token": true,
|
||||
"loop_shimmer_animation": false,
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"show_publish_state": true,
|
||||
"Creator.Room.EnableRoomTokens": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "hide cheers for new",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_new_data_token",
|
||||
"show_cheers_data_token",
|
||||
"hide_cheers_for_new"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0ryEs3H1VWZb3WPIHr7+lXa8JqCdzorsSJEHdrdlDGg=": {
|
||||
"name": "0ryEs3H1VWZb3WPIHr7+lXa8JqCdzorsSJEHdrdlDGg=",
|
||||
"value": {
|
||||
"isTokenStoreEnabled": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"isTokenStoreEnabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Pn8NOMWOfv/ridTE3yoeMtou8a+bti968plaWejdsD4=": {
|
||||
"name": "Pn8NOMWOfv/ridTE3yoeMtou8a+bti968plaWejdsD4=",
|
||||
"value": {
|
||||
"enable_retain_ui_state": true,
|
||||
"show_watch_button_in_menus": false,
|
||||
"portal_buttons_act_like_tabs": false,
|
||||
"portal_always_present": false,
|
||||
"enable_retain_cursor_position": true,
|
||||
"retain_ui_state_duration": 60,
|
||||
"use_dir_nav": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Retain UI State",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_retain_ui_state"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"WrrcWd2/SCmpjZbmFn6B7Ki/hf+qfNiBB4zAlzyBJWc=": {
|
||||
"name": "WrrcWd2/SCmpjZbmFn6B7Ki/hf+qfNiBB4zAlzyBJWc=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreAccessories",
|
||||
"CuratedList_StoreAppearance",
|
||||
"CuratedList_StoreCategories",
|
||||
"CuratedList_StoreClothing",
|
||||
"CuratedList_StoreConsumables",
|
||||
"CuratedList_StoreFeatured",
|
||||
"CuratedList_StoreInventions",
|
||||
"CuratedList_StoreOrientation",
|
||||
"CuratedList_StoreStyling"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"DTd+aVoNZrOtuN/ltpNZsXYM2EIJGOQeTdxqfLreKcE=": {
|
||||
"name": "DTd+aVoNZrOtuN/ltpNZsXYM2EIJGOQeTdxqfLreKcE=",
|
||||
"value": {
|
||||
"CuratedList_AppNavPortalPanel": "Discovery.PageSource.AppNavPortalPanel"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_AppNavPortalPanel"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"2q4KpHeEZuNhDvbg1NLqpfre39+fazIIoVaFlYxJsXU=": {
|
||||
"name": "2q4KpHeEZuNhDvbg1NLqpfre39+fazIIoVaFlYxJsXU=",
|
||||
"value": {
|
||||
"card_variant_name": "small",
|
||||
"limited_badge_enabled": true,
|
||||
"limited_badge_details": true,
|
||||
"limited_time_display_days": 0,
|
||||
"limited_badge_threshold_days": 7
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Hide over 7 days",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"limited_badge_threshold_days",
|
||||
"limited_badge_details"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"3Qn8T3i043Ln6Dus46OeCf44U+KCAiulbJBulwDd3FQ=": {
|
||||
"name": "3Qn8T3i043Ln6Dus46OeCf44U+KCAiulbJBulwDd3FQ=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xhS08/wP3QASibGzbnr7rtpXPXXVB+2e+5g7hj8gi04=": {
|
||||
"name": "xhS08/wP3QASibGzbnr7rtpXPXXVB+2e+5g7hj8gi04=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_CommunityBoard"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"vzXPoqF1Qwkv0KTxbu6NcQ+Z82kmfNHxFKjihw2oF+8=": {
|
||||
"name": "vzXPoqF1Qwkv0KTxbu6NcQ+Z82kmfNHxFKjihw2oF+8=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_credits_text",
|
||||
"show_credits_meter"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"d8/aqRm+u7c56zZEiuQQrtJzEkMlpEdjbLgkBe5oSiw=": {
|
||||
"name": "d8/aqRm+u7c56zZEiuQQrtJzEkMlpEdjbLgkBe5oSiw=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_remote_no_credit"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tLO583qKTthnh06kwddvfh1VZ7ecDnxaOJXE3T6YVts=": {
|
||||
"name": "tLO583qKTthnh06kwddvfh1VZ7ecDnxaOJXE3T6YVts=",
|
||||
"value": {
|
||||
"rrplus_paywall_type": "prevent_confirming_creation",
|
||||
"number_of_saved_outfits": 100,
|
||||
"show_mirror_button": true,
|
||||
"show_top_level_tab": true,
|
||||
"show_customizer_card": true,
|
||||
"show_back_facing_custom_shirt_preview": false,
|
||||
"show_new_customizer_buttons": true,
|
||||
"show_undo_redo_buttons": true,
|
||||
"default_custom_shirt_price": 1000
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "All Three",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_top_level_tab",
|
||||
"show_mirror_button",
|
||||
"show_customizer_card"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Sy84M4X94mVWO4lr7s+s27/14kth/Crkp7whrmhY5Z0=": {
|
||||
"name": "Sy84M4X94mVWO4lr7s+s27/14kth/Crkp7whrmhY5Z0=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": false,
|
||||
"rrui_notifications_enabled": false,
|
||||
"rrui_people_menu_enabled": false,
|
||||
"new_portfolio_page_enabled": false,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "featured",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_watch_architecture_enabled",
|
||||
"rrui_notifications_enabled",
|
||||
"rrui_people_menu_enabled",
|
||||
"new_portfolio_page_enabled",
|
||||
"home_banner"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zz4Ml1Hjcoeiz8RBNro5U+sNOmljU7+h3AanSApmrLc=": {
|
||||
"name": "zz4Ml1Hjcoeiz8RBNro5U+sNOmljU7+h3AanSApmrLc=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "PlayTabNoHistory",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"play_tab_enabled",
|
||||
"play_button_enabled",
|
||||
"browser_tabs_preserve_history_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7Az/ODeWxyEJYBtA6vr9Vrxg1y5pEmLD+qxr3PFQYac=": {
|
||||
"name": "7Az/ODeWxyEJYBtA6vr9Vrxg1y5pEmLD+qxr3PFQYac=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Play_Home_Hot_2",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"multi_row_home",
|
||||
"multi_row_play",
|
||||
"multi_row_enabled",
|
||||
"multi_row_background_color"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"TYEIElGK3foNrsn9WEKwOENAevQDgzJR9Jr1dKFpPgg=": {
|
||||
"name": "TYEIElGK3foNrsn9WEKwOENAevQDgzJR9Jr1dKFpPgg=",
|
||||
"value": {
|
||||
"prevent_ledge_jittering": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Ledge Bug Fixed",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"YK+/itWfLhaLCSo+QFnwF7oSVrShsU6TrFlFnMsKrXI=": {
|
||||
"name": "YK+/itWfLhaLCSo+QFnwF7oSVrShsU6TrFlFnMsKrXI=",
|
||||
"value": {
|
||||
"is_new_rrplus_page": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_new_rrplus_page"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"pS7SuUgaPqKWwTdYlQb6rtCJONy4SYUYXvZVfgQ+WGw=": {
|
||||
"name": "pS7SuUgaPqKWwTdYlQb6rtCJONy4SYUYXvZVfgQ+WGw=",
|
||||
"value": {
|
||||
"prevent-friend-request-spam": true,
|
||||
"Friends.FriendRequestCooldownSeconds": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "3 Seconds",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BER3JNpNGaZ0dMmr+c+w8BsBxiw+8C5ihMtgjqKDKYM=": {
|
||||
"name": "BER3JNpNGaZ0dMmr+c+w8BsBxiw+8C5ihMtgjqKDKYM=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Lifecycle.PlayMenuCarouselHighlighting",
|
||||
"GroupName"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"m/PEkbzwlocKlfCLXBIaubPGI8m7+ief6xPaY1qJwoQ=": {
|
||||
"name": "m/PEkbzwlocKlfCLXBIaubPGI8m7+ief6xPaY1qJwoQ=",
|
||||
"value": {
|
||||
"UseRowArrow": true,
|
||||
"UseBlueRoomName": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Row Arrows",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"oPOsMymNjrfTNY8oqsA0GKHS1CiY4GE4YCSQt4pOaPw=": {
|
||||
"name": "oPOsMymNjrfTNY8oqsA0GKHS1CiY4GE4YCSQt4pOaPw=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CanBulkWishlistGift"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"68SQtTmxn1Z4ulpglOFI8NDneXA2hxAecfXAzssGht4=": {
|
||||
"name": "68SQtTmxn1Z4ulpglOFI8NDneXA2hxAecfXAzssGht4=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"conditionally_spawn_scroll_bars"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zNXjksdCnADleUXn3EzQtXxjNlp9xaJiUutYLIBd/Jk=": {
|
||||
"name": "zNXjksdCnADleUXn3EzQtXxjNlp9xaJiUutYLIBd/Jk=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"conditionally_spawn_if_contains_room_warnings",
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1oYhVAlznctdgPt+rz9//t9DmT+mScFJy3gqRuT8OXM=": {
|
||||
"name": "1oYhVAlznctdgPt+rz9//t9DmT+mScFJy3gqRuT8OXM=",
|
||||
"value": {
|
||||
"lazy_load_rec_rally_hud": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9wOcb0UXbfIxB8en0XMBJY0mgf0MILZ4/lbBvkQofWQ=": {
|
||||
"name": "9wOcb0UXbfIxB8en0XMBJY0mgf0MILZ4/lbBvkQofWQ=",
|
||||
"value": {
|
||||
"use_new_room_card": true,
|
||||
"use_new_club_event_card": true,
|
||||
"use_new_item_cards": true,
|
||||
"use_new_account_cards": true,
|
||||
"use_vnext_db_room_carousels": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New Item Cards",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_new_item_cards"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"eyUw3r8UxER33OeeUk+Fpl0J5LxgbmDgnBFMaIOifbU=": {
|
||||
"name": "eyUw3r8UxER33OeeUk+Fpl0J5LxgbmDgnBFMaIOifbU=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowBottomsAndShoesTabs"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"z89DFuwb0yR6YIg+HsLxBQ/zDz595zqMIZqI3+HbF1c=": {
|
||||
"name": "z89DFuwb0yR6YIg+HsLxBQ/zDz595zqMIZqI3+HbF1c=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": true,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control ",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"store_item_prefab_variant_name",
|
||||
"hide_rarity",
|
||||
"use_buy_cta_button",
|
||||
"hide_prediscount_price"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7bbJm8jSbS2Y0DrQo/KwrndfF0zfVUGafmEfid/dFJM=": {
|
||||
"name": "7bbJm8jSbS2Y0DrQo/KwrndfF0zfVUGafmEfid/dFJM=",
|
||||
"value": {
|
||||
"Retention.Coordinate.BeaconMVPEnabled": true,
|
||||
"Tip:Visibility": 1,
|
||||
"Retention.Coordinate.ReceiveAnnouncement": true
|
||||
},
|
||||
"rule_id": "2HodNrKbmztrxVhiupQYlb:override",
|
||||
"group": "2HodNrKbmztrxVhiupQYlb:override",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Retention.Coordinate.BeaconMVPEnabled",
|
||||
"Tip:Visibility",
|
||||
"Retention.Coordinate.ReceiveAnnouncement"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RYvHMbLEwCBruTvdK/4A6tK5JxCfcIsGMY81oWtm+RA=": {
|
||||
"name": "RYvHMbLEwCBruTvdK/4A6tK5JxCfcIsGMY81oWtm+RA=",
|
||||
"value": {
|
||||
"DefaultUnlockedValueThreshold": 3,
|
||||
"DailyTokenCapOverride": 6217911,
|
||||
"MaximumPostActivityRewardCountOverride": 10,
|
||||
"GiftContextOverride": "GameRewards",
|
||||
"RewardProbabilityOverride": 1,
|
||||
"TokenGiftDrop:Common": 3397,
|
||||
"TokenGiftDrop:Common:Subscribers": 4013,
|
||||
"TokenGiftDrop:Uncommon": 3396,
|
||||
"TokenGiftDrop:Uncommon:Subscribers": 4014,
|
||||
"TokenGiftDrop:Rare": 3395,
|
||||
"TokenGiftDrop:Rare:Subscribers": 4015,
|
||||
"TokenGiftDrop:Epic": 3394,
|
||||
"TokenGiftDrop:Epic:Subscribers": 4016,
|
||||
"IsSearchWithTagsEnabled": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"DailyTokenCapOverride",
|
||||
"MaximumPostActivityRewardCountOverride",
|
||||
"GiftContextOverride",
|
||||
"RewardProbabilityOverride",
|
||||
"TokenGiftDrop:Common",
|
||||
"TokenGiftDrop:Common:Subscribers",
|
||||
"TokenGiftDrop:Uncommon",
|
||||
"TokenGiftDrop:Uncommon:Subscribers",
|
||||
"TokenGiftDrop:Rare",
|
||||
"TokenGiftDrop:Rare:Subscribers",
|
||||
"TokenGiftDrop:Epic",
|
||||
"TokenGiftDrop:Epic:Subscribers"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"43I6e+FsWAohhYFgbz2W7M9lmrFDiHLB6Fc3Hyfv/S4=": {
|
||||
"name": "43I6e+FsWAohhYFgbz2W7M9lmrFDiHLB6Fc3Hyfv/S4=",
|
||||
"value": {
|
||||
"enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"i+sv3LQGbkV8wFTqjPUyRBhThooGYryXS3wh+FWqTrE=": {
|
||||
"name": "i+sv3LQGbkV8wFTqjPUyRBhThooGYryXS3wh+FWqTrE=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"orientation_version",
|
||||
"supports_identity_creation",
|
||||
"target_orientation_room_name",
|
||||
"allow_skip_orientation"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"f/FQVV1d8njfupSfd+J57jfk9zB6cWl5mzPHJDeelCE=": {
|
||||
"name": "f/FQVV1d8njfupSfd+J57jfk9zB6cWl5mzPHJDeelCE=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Default to Tops Tab",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"default_tab_name"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"vMLHwJm6MaRNJccYIvUX5SRV2sJaBcdZkUO6DZZZ7j0=": {
|
||||
"name": "vMLHwJm6MaRNJccYIvUX5SRV2sJaBcdZkUO6DZZZ7j0=",
|
||||
"value": {
|
||||
"CuratedList_AppNavPortalPanel": "Discovery.PageSource.AppNavPortalPanel"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_AppNavPortalPanel"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"LSf1q+XUy/058lb75+rpJfO59zBYAKUGho+jxJpc2HY=": {
|
||||
"name": "LSf1q+XUy/058lb75+rpJfO59zBYAKUGho+jxJpc2HY=",
|
||||
"value": {
|
||||
"show_revamped_upsell_reward_picker": true,
|
||||
"upsell_decline_cooldown_seconds": 86400
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_revamped_upsell_reward_picker",
|
||||
"upsell_decline_cooldown_seconds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"iBAGFZxCdl9mvwRDVtHOV3CLx7tiR1yHxtjW38Q3bf4=": {
|
||||
"name": "iBAGFZxCdl9mvwRDVtHOV3CLx7tiR1yHxtjW38Q3bf4=",
|
||||
"value": {
|
||||
"card_variant_name": "small",
|
||||
"limited_badge_enabled": true,
|
||||
"limited_badge_details": false,
|
||||
"limited_time_display_days": 0,
|
||||
"limited_badge_threshold_days": 7
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"limited_badge_enabled",
|
||||
"limited_badge_details"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"MpCiyG1aFFqwsSh1F5HCMTcA99cnyzWv0MyFDmJ6L64=": {
|
||||
"name": "MpCiyG1aFFqwsSh1F5HCMTcA99cnyzWv0MyFDmJ6L64=",
|
||||
"value": {
|
||||
"boot_strategy": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"boot_strategy"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"P3i4L0H+QFiiLeIa8spdgF6zZiTAN4Mv6X+DVeeTsFw=": {
|
||||
"name": "P3i4L0H+QFiiLeIa8spdgF6zZiTAN4Mv6X+DVeeTsFw=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ONKAe+VAYbSCEDJlLdIpEA2GT8PN0lLT03fpwEOZeYs=": {
|
||||
"name": "ONKAe+VAYbSCEDJlLdIpEA2GT8PN0lLT03fpwEOZeYs=",
|
||||
"value": {
|
||||
"FeatureEnabled": false,
|
||||
"DisplayP2PChatButton": false,
|
||||
"DisplayBadgeOnWatchIcon": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "930626607",
|
||||
"gateValue": "true",
|
||||
"ruleID": "4gQtmddP2Gauv81AqWUi7a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"1dOmxztccQ7FofBs5/FwaML+HtvAvdygSJ22VJv34W0=": {
|
||||
"name": "1dOmxztccQ7FofBs5/FwaML+HtvAvdygSJ22VJv34W0=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=22;new=21;recroomoriginals=20;continueplaying=19;myfavorites=18;friendsplaying=17;topearning=16;mysubscriptions=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0,
|
||||
"Group Name": "Control"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"PlayMenuCarouselOrdering_1",
|
||||
"Group Name",
|
||||
"PlayMenuCuratedCarouselsMap"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"IWH+N8diLffGmSHWo0TAWJ8kLUNFmvd/70+YC1sSCTo=": {
|
||||
"name": "IWH+N8diLffGmSHWo0TAWJ8kLUNFmvd/70+YC1sSCTo=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"carousel_order"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"60m1YCUD/Sk3mwLl+QV/rrNzWSJ+4me5jLJOp/PMCeE=": {
|
||||
"name": "60m1YCUD/Sk3mwLl+QV/rrNzWSJ+4me5jLJOp/PMCeE=",
|
||||
"value": {
|
||||
"isEnabled": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"VJLi5MMsQW8O/fAP5823YLZK0GcF1dyDUCyKQToPzjI=": {
|
||||
"name": "VJLi5MMsQW8O/fAP5823YLZK0GcF1dyDUCyKQToPzjI=",
|
||||
"value": {
|
||||
"load_backpack_tools_on_demand": true,
|
||||
"load_room_chat_preview_on_demand": false,
|
||||
"load_room_chat_expanded_on_demand": true,
|
||||
"split_room_chat_by_vr_and_non_vr": true,
|
||||
"only_load_in_vr": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "OptimizedSpawning",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"split_room_chat_by_vr_and_non_vr",
|
||||
"load_room_chat_expanded_on_demand"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"jO170EuE8I1UUbAGUr7hA+Hpk3Bt/fJhKQX7xXmlCCw=": {
|
||||
"name": "jO170EuE8I1UUbAGUr7hA+Hpk3Bt/fJhKQX7xXmlCCw=",
|
||||
"value": {
|
||||
"remove_dance": false,
|
||||
"remove_reach": false,
|
||||
"remove_emote": false,
|
||||
"remove_emotes_from_room_chat": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"remove_dance"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JKGzxF3kvrWJZ4LXzz6MA67r/jK4m9sBbNbJCKyfrhI=": {
|
||||
"name": "JKGzxF3kvrWJZ4LXzz6MA67r/jK4m9sBbNbJCKyfrhI=",
|
||||
"value": {
|
||||
"shouldReleaseOnCanvasHierarchyChange": false,
|
||||
"shouldReleaseOnVisibilityCheckerChange": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"dPkv84dTPAeEC8AsWAKjI7HhQaGaVFK24LEwwP7P2Xg=": {
|
||||
"name": "dPkv84dTPAeEC8AsWAKjI7HhQaGaVFK24LEwwP7P2Xg=",
|
||||
"value": {
|
||||
"blob_shadow_enabled": true,
|
||||
"force_high_quality": false,
|
||||
"test_config": "QRP GDRP GRP APPLY"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Blob Shadow",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"blob_shadow_enabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1195813645",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"5E7l/lwp2B3thedNpqxe1ivlyHMMewNfQZLaH/duTSc=": {
|
||||
"name": "5E7l/lwp2B3thedNpqxe1ivlyHMMewNfQZLaH/duTSc=",
|
||||
"value": {
|
||||
"tap_player_radial_menu_variant": "TEST1"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Radial Menu",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"tap_player_radial_menu_variant"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"y3b0Xl4ojjKO/Lanm/88PhG9kBwPp7fN6rkDE8/htHY=": {
|
||||
"name": "y3b0Xl4ojjKO/Lanm/88PhG9kBwPp7fN6rkDE8/htHY=",
|
||||
"value": {
|
||||
"force_besthttp_typeload": false,
|
||||
"delay_recnet_connect": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"delay_recnet_connect"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"p2akTJAcMMMe3vH5gwVZSaHJnHzc7ooN8Axb58sCGxg=": {
|
||||
"name": "p2akTJAcMMMe3vH5gwVZSaHJnHzc7ooN8Axb58sCGxg=",
|
||||
"value": {
|
||||
"NetworkBytes": -1,
|
||||
"Rpcs": -1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "RoomInstanceID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"NetworkBytes",
|
||||
"Rpcs"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"4OimBUEtprfbeMIBVc1W6f+Et2XTzqehmJF+HPHIAcg=": {
|
||||
"name": "4OimBUEtprfbeMIBVc1W6f+Et2XTzqehmJF+HPHIAcg=",
|
||||
"value": {
|
||||
"optimize_expresso_ui": false,
|
||||
"optimize_raycasts": false,
|
||||
"optimize_nearest_canvas": false,
|
||||
"cache_uri_route_strings": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"cache_uri_route_strings"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Z/Ft7aL5Q4D/rvaW2YapcXQq2xohDK0LzzAqWu5FKR4=": {
|
||||
"name": "Z/Ft7aL5Q4D/rvaW2YapcXQq2xohDK0LzzAqWu5FKR4=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"display_order",
|
||||
"campaignIds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"H//n5Qar4Wg5z7DIw75TGrGvq/MaFxGxReKgsF4Au9I=": {
|
||||
"name": "H//n5Qar4Wg5z7DIw75TGrGvq/MaFxGxReKgsF4Au9I=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test 2 - Shortened Multiplayer",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"target_orientation_room_name",
|
||||
"orientation_version",
|
||||
"orientation_sub_room_names"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Vn9hXnB/bjT0N3FoXVhnz/lnOwGiCpv7fYrXglW5PFo=": {
|
||||
"name": "Vn9hXnB/bjT0N3FoXVhnz/lnOwGiCpv7fYrXglW5PFo=",
|
||||
"value": {
|
||||
"show_store_items_in_room_details": false,
|
||||
"carousel_title": "Recommended for this Room"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_store_items_in_room_details"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"oATYWtP3uoJvm6b6BwGKsWi9zptVJNwwjkzVBuaHtBM=": {
|
||||
"name": "oATYWtP3uoJvm6b6BwGKsWi9zptVJNwwjkzVBuaHtBM=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bwRRhd2yvLA7SN0FIVHxvs31vsblUnQsynvSEEiJ6C8=": {
|
||||
"name": "bwRRhd2yvLA7SN0FIVHxvs31vsblUnQsynvSEEiJ6C8=",
|
||||
"value": {
|
||||
"should_see_r2_is_default_flow": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"1gxeuVOU0tru/Xk0Gvy0xVyNh/dVWKMtlrZG6PAH3SI=": {
|
||||
"name": "1gxeuVOU0tru/Xk0Gvy0xVyNh/dVWKMtlrZG6PAH3SI=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_server_thumbnail"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fgPd2rrYtROEU9Amv19yURkVMNTviSMRvxD3wApK9+4=": {
|
||||
"name": "fgPd2rrYtROEU9Amv19yURkVMNTviSMRvxD3wApK9+4=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_CommunityBoard"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"N8itderfiTS+0f/Wj6OUDh00oPFUllcJ12RnKSmtHdI=": {
|
||||
"name": "N8itderfiTS+0f/Wj6OUDh00oPFUllcJ12RnKSmtHdI=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"D0LZq453142W499fQrA7vWOMjs7ILdz71u5Hs+b0jFk=": {
|
||||
"name": "D0LZq453142W499fQrA7vWOMjs7ILdz71u5Hs+b0jFk=",
|
||||
"value": {
|
||||
"duration_minutes": 1440,
|
||||
"straight_to_room": false,
|
||||
"GroupName": "CategorySelect_24h"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "CategorySelect_24h",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"wVrvZ6tCCY2fw6zPDJ8RQODGSINUXVkGecXbEFDaiU8=": {
|
||||
"name": "wVrvZ6tCCY2fw6zPDJ8RQODGSINUXVkGecXbEFDaiU8=",
|
||||
"value": {
|
||||
"show_share_in_chat_button": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"/0xCncAYxzQQc7p1dJqspJZgnIUejlpnS25aPz8gCuk=": {
|
||||
"name": "/0xCncAYxzQQc7p1dJqspJZgnIUejlpnS25aPz8gCuk=",
|
||||
"value": {
|
||||
"height_adjustment": -0.1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"height_adjustment"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ZSIEGEuT8zPjnJ+8/tc751hu9ICkdrN8Xp2YZauzmJQ=": {
|
||||
"name": "ZSIEGEuT8zPjnJ+8/tc751hu9ICkdrN8Xp2YZauzmJQ=",
|
||||
"value": {
|
||||
"search_logging_on": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "On",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7H/V51WpDzD/GOy63O/mMZYMWl5h79Su0Nw7owKVKcI=": {
|
||||
"name": "7H/V51WpDzD/GOy63O/mMZYMWl5h79Su0Nw7owKVKcI=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "No Caching",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"router_enable_prefill_routes",
|
||||
"router_override_hidden_page_pool_size",
|
||||
"router_hidden_page_pool_size"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"sylrS1Y3ihJCgdRSc8+zH7n4iBkuvcBANMqooBlsHmM=": {
|
||||
"name": "sylrS1Y3ihJCgdRSc8+zH7n4iBkuvcBANMqooBlsHmM=",
|
||||
"value": {
|
||||
"allow_juniors_quick_chat": false,
|
||||
"include_online_friends": true,
|
||||
"friends_default_tab": true,
|
||||
"default_friends_if_people_empty": false,
|
||||
"show_detailed_player_search_cards": true,
|
||||
"show_empty_search_call_to_action": true,
|
||||
"hide_local_people_search": false,
|
||||
"show_search_players_text_for_remote_search": true,
|
||||
"show_progression_on_watch_home": true,
|
||||
"show_progression_profile": true,
|
||||
"show_progression_notifications": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test #2",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_search_players_text_for_remote_search",
|
||||
"hide_local_people_search"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"2i89vubWWWGou4cutQvrKQSHAXJuPVCoPvm9TQ7ukCc=": {
|
||||
"name": "2i89vubWWWGou4cutQvrKQSHAXJuPVCoPvm9TQ7ukCc=",
|
||||
"value": {
|
||||
"block_control_at_start": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"mzMcixGVEb35Plc/evFIJeRq70iJX1+BrhK+2xjUYtk=": {
|
||||
"name": "mzMcixGVEb35Plc/evFIJeRq70iJX1+BrhK+2xjUYtk=",
|
||||
"value": {
|
||||
"third_person_feature_set": "FULL",
|
||||
"persistence": false,
|
||||
"Tip:Visibility": 0,
|
||||
"group_dance_with_gestures": true,
|
||||
"reduce_dance_options": false,
|
||||
"default_to_third_person": false,
|
||||
"toggle_in_orientation": true,
|
||||
"pinch_to_zoom": true,
|
||||
"tutorial_in_orientation": true,
|
||||
"new_melee": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test - Full",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"third_person_feature_set",
|
||||
"persistence"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"5r+ywf0jqi9hPOMPl8jeGsd2lBg3vwyN2bse2f8KHKU=": {
|
||||
"name": "5r+ywf0jqi9hPOMPl8jeGsd2lBg3vwyN2bse2f8KHKU=",
|
||||
"value": {
|
||||
"load_backpack_tools_on_demand": true,
|
||||
"load_room_chat_preview_on_demand": false,
|
||||
"load_room_chat_expanded_on_demand": true,
|
||||
"split_room_chat_by_vr_and_non_vr": true,
|
||||
"only_load_in_vr": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"only_load_in_vr"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zUvmIwh+uxl68JSRMbI3mSWLdI21VDWAjaG07j+yFxM=": {
|
||||
"name": "zUvmIwh+uxl68JSRMbI3mSWLdI21VDWAjaG07j+yFxM=",
|
||||
"value": {
|
||||
"isEnabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1175906151",
|
||||
"gateValue": "true",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Hv8Eqfsh9hd15L1kcBCJdxZMNqXCfWsD0+AymnFpnsQ=": {
|
||||
"name": "Hv8Eqfsh9hd15L1kcBCJdxZMNqXCfWsD0+AymnFpnsQ=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": false,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": true,
|
||||
"options_count_per_platform": {
|
||||
"mobile": 4,
|
||||
"pc": 4,
|
||||
"vr": 4
|
||||
},
|
||||
"AutoCompleteIndexName": "room-autocomplete_inc_popular_rooms"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"autosearch_disabled",
|
||||
"hide_filters",
|
||||
"show_history_with_chips",
|
||||
"show_history_with_button",
|
||||
"show_history"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"SSc/+vvwCGlJHq2WAw4fCWJyq7xn9Nmq4u2FbXaFWEg=": {
|
||||
"name": "SSc/+vvwCGlJHq2WAw4fCWJyq7xn9Nmq4u2FbXaFWEg=",
|
||||
"value": {
|
||||
"third_person_feature_set": "FULL",
|
||||
"persistence": true,
|
||||
"Tip:Visibility": 0,
|
||||
"group_dance_with_gestures": true,
|
||||
"reduce_dance_options": false,
|
||||
"default_to_third_person": false,
|
||||
"toggle_in_orientation": true,
|
||||
"pinch_to_zoom": true,
|
||||
"tutorial_in_orientation": true,
|
||||
"new_melee": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Persistent",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"persistence"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"CNM/f22pAI/gj6EsA9yIl2JMJTgNygWMYSxpUIX7yWE=": {
|
||||
"name": "CNM/f22pAI/gj6EsA9yIl2JMJTgNygWMYSxpUIX7yWE=",
|
||||
"value": {
|
||||
"block_and_mute_before_report": true,
|
||||
"block_and_mute_after_report": false,
|
||||
"mute_only_after_report": false,
|
||||
"block_only_after_report": false,
|
||||
"show_after_report_dialog": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Both before",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"block_and_mute_before_report",
|
||||
"block_and_mute_after_report",
|
||||
"mute_only_after_report",
|
||||
"block_only_after_report",
|
||||
"show_after_report_dialog"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"aYEY3RLUdXLcoeFItZ0RuIS/k16lt9P5BulwiUc57YM=": {
|
||||
"name": "aYEY3RLUdXLcoeFItZ0RuIS/k16lt9P5BulwiUc57YM=",
|
||||
"value": {
|
||||
"removePortraitMode": false,
|
||||
"recnet_deserialize_json_on_background_thread_length_threshold": 1000,
|
||||
"recnet_use_besthttp_request_queue": false,
|
||||
"parallelize_photon_connection": true,
|
||||
"start_critical_operations_early": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"start_critical_operations_early"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "795959371",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7zLAnzZ4AiAvCAgX6xB3ZL:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ZHjmJSfQ5bx3FicUtwCLmQENeCew/WRuA3qfj0p0i4A=": {
|
||||
"name": "ZHjmJSfQ5bx3FicUtwCLmQENeCew/WRuA3qfj0p0i4A=",
|
||||
"value": {
|
||||
"avatar_scalability_active": false,
|
||||
"lod0_budget": 10000,
|
||||
"lod1_budget": 10000,
|
||||
"updates_wait_for_empty_build_queue": false,
|
||||
"use_dynamic_budgets": false,
|
||||
"dynamic_budget_mb": 10000,
|
||||
"dynamic_budget_lod0_cost_mb": 5,
|
||||
"dynamic_budget_lod1_cost_mb": 4,
|
||||
"dynamic_budget_lod2_cost_mb": 0.4,
|
||||
"quest_bean_job_builds": false
|
||||
},
|
||||
"rule_id": "targetingGate",
|
||||
"group": "targetingGate",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"avatar_scalability_active",
|
||||
"lod0_budget",
|
||||
"lod1_budget",
|
||||
"updates_wait_for_empty_build_queue",
|
||||
"use_dynamic_budgets",
|
||||
"dynamic_budget_mb"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "294724160",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"yXpjpAgEsCk7RHgE+cDVN66R/w4soG0ZJC25/dKPToE=": {
|
||||
"name": "yXpjpAgEsCk7RHgE+cDVN66R/w4soG0ZJC25/dKPToE=",
|
||||
"value": {
|
||||
"should_lazy_load_emojis": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ryG2gAeJN5upnSn4AvXcwbBbRjS0RhS8sRM9BH3gs9Y=": {
|
||||
"name": "ryG2gAeJN5upnSn4AvXcwbBbRjS0RhS8sRM9BH3gs9Y=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Variant B",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hide_unowned_items",
|
||||
"show_link_to_store",
|
||||
"merge_custom_and_fp_shirts",
|
||||
"show_featured_store_row",
|
||||
"featured_item_algo_endpoint"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"B9Y5C5FwzhDmrbUF8mY46XaAIzHU1jWbG/HQ9rYyyFA=": {
|
||||
"name": "B9Y5C5FwzhDmrbUF8mY46XaAIzHU1jWbG/HQ9rYyyFA=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"store_item_prefab_variant_name",
|
||||
"hide_rarity",
|
||||
"use_buy_cta_button",
|
||||
"hide_prediscount_price"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JFMP4GkWyPaBJUWar1N1DHstqh/k1tx51yt6zQ2TDH0=": {
|
||||
"name": "JFMP4GkWyPaBJUWar1N1DHstqh/k1tx51yt6zQ2TDH0=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"iosHighFPS"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ki4ENf+IyiST9tegzJj8WkDu79SK8lLX2bD777z9lFE=": {
|
||||
"name": "ki4ENf+IyiST9tegzJj8WkDu79SK8lLX2bD777z9lFE=",
|
||||
"value": {
|
||||
"place_r2_support_enabled": true,
|
||||
"place_improved_rotation_enabled": true,
|
||||
"place_touch_depth_enabled": true,
|
||||
"place_touch_tutorial_enabled": true,
|
||||
"place_max_tool_distance": 3,
|
||||
"place_improved_large_objects_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"place_r2_support_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"5BnawtlQ+OEbNs62YeOy9rqgi0vKYRfFSexBqjqJGR8=": {
|
||||
"name": "5BnawtlQ+OEbNs62YeOy9rqgi0vKYRfFSexBqjqJGR8=",
|
||||
"value": {
|
||||
"IsSearchWithTagsEnabled": true,
|
||||
"UseElasticsearch": true,
|
||||
"UseSecondaryOrdering": true,
|
||||
"UseFuzzySearch": true,
|
||||
"SearchDescription": false,
|
||||
"NameRelevanceBoost": 1,
|
||||
"TagRelevanceBoost": 1,
|
||||
"DescriptionRelevanceBoost": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"IsSearchWithTagsEnabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"s13HOEDhi3VkwhGl/DipATWOC1U/T6YCS0gSCW99Rs4=": {
|
||||
"name": "s13HOEDhi3VkwhGl/DipATWOC1U/T6YCS0gSCW99Rs4=",
|
||||
"value": {
|
||||
"use_item_rrp_upsell": true,
|
||||
"show_for_save": false,
|
||||
"show_for_publish": false,
|
||||
"RecRoomPlusSignUpBonusIds": [
|
||||
3
|
||||
]
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Upsell Offer 6000 to 10000 Tokens",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"RecRoomPlusSignUpBonusIds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ZQ6jLR/H/ILtFP0hYXnqHhHuZYvn7ob+tPVGwleHF+I=": {
|
||||
"name": "ZQ6jLR/H/ILtFP0hYXnqHhHuZYvn7ob+tPVGwleHF+I=",
|
||||
"value": {
|
||||
"algo_name": "text-embedding-004",
|
||||
"algo_version": "0",
|
||||
"return_sorted_by_offline_ranking": true,
|
||||
"number_of_entities_to_return": 100,
|
||||
"minimum_offline_ranking_score": 20,
|
||||
"minimum_similarity_score": 0,
|
||||
"embedding_dimension": 768
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"algo_name",
|
||||
"algo_version",
|
||||
"embedding_dimension"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"wdjbgIhElzrtfqq9AWe9Viy/yoT0PFQOsl1vNG8BO3E=": {
|
||||
"name": "wdjbgIhElzrtfqq9AWe9Viy/yoT0PFQOsl1vNG8BO3E=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_MobileHome"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hNuvY6gAFhQcdG4aIZC/1pt1UwedKzQgGr5FkihPV3o=": {
|
||||
"name": "hNuvY6gAFhQcdG4aIZC/1pt1UwedKzQgGr5FkihPV3o=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ID7cJN8MUBbKgCt5ldkQKjUWsFBal37CUHJtVEFVXP0=": {
|
||||
"name": "ID7cJN8MUBbKgCt5ldkQKjUWsFBal37CUHJtVEFVXP0=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"new_portfolio_page_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ua4yO6q9wuNuaVhBr8fQ/w9ZWMrocY5umoAX9+zJk3Q=": {
|
||||
"name": "Ua4yO6q9wuNuaVhBr8fQ/w9ZWMrocY5umoAX9+zJk3Q=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=23;new=22;recroomoriginals=21;continueplaying=20;rising=19;myfavorites=18;friendsplaying=17;topearning=16;mysubscriptions=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Add Rising",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"PlayMenuCarouselOrdering_1"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ZcJWz9L80Lt4aRMUdGU70EUyP2GkBMo3kMjx8Q7haRY=": {
|
||||
"name": "ZcJWz9L80Lt4aRMUdGU70EUyP2GkBMo3kMjx8Q7haRY=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems",
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems",
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems",
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems",
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems",
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems",
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems",
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems",
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems",
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems",
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems",
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems",
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems",
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems",
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems",
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"M/nuELbZl/vtfN9UKRqbm6fza58rdaBluOUqsyHNhoY=": {
|
||||
"name": "M/nuELbZl/vtfN9UKRqbm6fza58rdaBluOUqsyHNhoY=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"GroupName",
|
||||
"remove_horizontal_scroll",
|
||||
"home_menu_disable_horizontal_scroll",
|
||||
"remove_chips",
|
||||
"remove_metadata_from_cards",
|
||||
"smallest_size",
|
||||
"mid_size",
|
||||
"largest_size",
|
||||
"PlayMenuCarouselOrdering_1",
|
||||
"PlayMenuCuratedCarouselsMap",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting",
|
||||
"view_more_button_text",
|
||||
"open_content_menu",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled",
|
||||
"ccu_load_synchronously",
|
||||
"abbreviate_cheer_counts",
|
||||
"multi_row_home",
|
||||
"multi_row_play",
|
||||
"multi_row_enabled",
|
||||
"multi_row_background_color"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"6gI1Q0HZBA2CIDI2xKc19KTTEvES4z1k4+5JoaZWpPY=": {
|
||||
"name": "6gI1Q0HZBA2CIDI2xKc19KTTEvES4z1k4+5JoaZWpPY=",
|
||||
"value": {
|
||||
"allow_juniors_quick_chat": false,
|
||||
"include_online_friends": true,
|
||||
"friends_default_tab": true,
|
||||
"default_friends_if_people_empty": false,
|
||||
"show_detailed_player_search_cards": true,
|
||||
"show_empty_search_call_to_action": true,
|
||||
"hide_local_people_search": false,
|
||||
"show_search_players_text_for_remote_search": true,
|
||||
"show_progression_on_watch_home": true,
|
||||
"show_progression_profile": true,
|
||||
"show_progression_notifications": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_detailed_player_search_cards"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"0323iPy7yLtu211IpKmOBKaaUDRUq6+kksosDKWSCBA=": {
|
||||
"name": "0323iPy7yLtu211IpKmOBKaaUDRUq6+kksosDKWSCBA=",
|
||||
"value": {
|
||||
"store_featured_order": [
|
||||
"hotitem",
|
||||
"banner",
|
||||
"fivestaritem",
|
||||
"newitem",
|
||||
"allskus",
|
||||
"skinsitem",
|
||||
"rrplusitem",
|
||||
"trendingcustomitem",
|
||||
"hotcustomitem",
|
||||
"hotinvention",
|
||||
"mysubscriptioninvention",
|
||||
"hairdyeitem"
|
||||
]
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"p6CEAqAO2gHcMwVGmVE6PByi6sRB3UOeFdLTt9p/xAU=": {
|
||||
"name": "p6CEAqAO2gHcMwVGmVE6PByi6sRB3UOeFdLTt9p/xAU=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_optimized_community_board_prefab"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QiQAMYne3OYI1jRbyfZ+eeZUc/58Wkn7rQofe7Z5vGo=": {
|
||||
"name": "QiQAMYne3OYI1jRbyfZ+eeZUc/58Wkn7rQofe7Z5vGo=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"conditionally_spawn_playmenu_banner"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"uA+DRYoPKxL3tihr580EihlvrF7wIgA4bpjPMZwHC6Y=": {
|
||||
"name": "uA+DRYoPKxL3tihr580EihlvrF7wIgA4bpjPMZwHC6Y=",
|
||||
"value": {
|
||||
"remove_vr_input_configs": false,
|
||||
"remove_laser_teleporter": false,
|
||||
"lazy_load_objective_log_hud": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"remove_vr_input_configs"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9ILU8v6XbPSE1PRobOrfi3IYd/2kanF9h13tXjnOF1c=": {
|
||||
"name": "9ILU8v6XbPSE1PRobOrfi3IYd/2kanF9h13tXjnOF1c=",
|
||||
"value": {
|
||||
"variant": "LARGE_LIVE_TQ"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test - Large Live TQ",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"variant"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9jY7aPiYHjHFFauFqxFEUgO668nvjjHsdjktTJQFdt4=": {
|
||||
"name": "9jY7aPiYHjHFFauFqxFEUgO668nvjjHsdjktTJQFdt4=",
|
||||
"value": {
|
||||
"is_enabled": false,
|
||||
"notice_on_visit_probability": 0,
|
||||
"notice_duration_seconds": 5,
|
||||
"message_string": "Voice moderation is active in this room.",
|
||||
"title_string": "Coach is listening!",
|
||||
"moderated_player_only": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_enabled",
|
||||
"notice_on_visit_probability",
|
||||
"notice_duration_seconds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"OOGUKXhU7hCrFBWNYKHzpSbe6BYM1atqS6N4o/4ZgSU=": {
|
||||
"name": "OOGUKXhU7hCrFBWNYKHzpSbe6BYM1atqS6N4o/4ZgSU=",
|
||||
"value": {
|
||||
"streaming_mipmaps_enabled": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"oj8tCyj/QNZEK43tnkJinmYOmmK43lOxve5Z70yvnAg=": {
|
||||
"name": "oj8tCyj/QNZEK43tnkJinmYOmmK43lOxve5Z70yvnAg=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hidden_types"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qksOg6ChiRZIozV91D8vAexEr/SjqanaIL2jb//fqxg=": {
|
||||
"name": "qksOg6ChiRZIozV91D8vAexEr/SjqanaIL2jb//fqxg=",
|
||||
"value": {
|
||||
"show_ccu_count": false,
|
||||
"show_popular_badge": true,
|
||||
"pin_popular_room": false,
|
||||
"supported_carousels": [
|
||||
"SEARCH_ROOMS_WATCH"
|
||||
],
|
||||
"Discovery.CCUIconThresholds": "[ { \"Count\": 50, \"Label\": \"Hot\", \"Color\": \"#EA6F48\" } ]",
|
||||
"Discovery.PopularBadgeCCUThreshold": 50,
|
||||
"best_popular_card_threshold": -1,
|
||||
"room_list_join_or_create_count": 0,
|
||||
"room_list_join_only_count": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Badge",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"supported_carousels",
|
||||
"show_ccu_count",
|
||||
"show_popular_badge"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fc6uSZ8PzyD+2ie1D1rv7y27nLkDx25jBms7yYSyAvk=": {
|
||||
"name": "fc6uSZ8PzyD+2ie1D1rv7y27nLkDx25jBms7yYSyAvk=",
|
||||
"value": {
|
||||
"show_x_to_close": true,
|
||||
"show_ignore_for_now": false,
|
||||
"improve_room_chat_visuals": true,
|
||||
"add_room_chat_emojis": true,
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat": "false",
|
||||
"max_room_chat_preview_lines": 5,
|
||||
"max_room_chat_expanded_lines": 21,
|
||||
"show_select_unselect_all_button": false,
|
||||
"preselect_all_accounts": false,
|
||||
"show_modified_dont_invite_button": true,
|
||||
"show_modified_invite_button": false,
|
||||
"emphasize_reccenter_button": false,
|
||||
"should_autofocus": true,
|
||||
"should_autofocus_search": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"should_autofocus_search"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7NcYcNGWMxapfjrDQIyYNa2M8PPBvHA1J8MCZVNPda4=": {
|
||||
"name": "7NcYcNGWMxapfjrDQIyYNa2M8PPBvHA1J8MCZVNPda4=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"gJ1tjFxfa/CBrGDXGCFjuzS3GD+xNRLX8fe4AP5dIaU=": {
|
||||
"name": "gJ1tjFxfa/CBrGDXGCFjuzS3GD+xNRLX8fe4AP5dIaU=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tokens",
|
||||
"store",
|
||||
"dormskins",
|
||||
"recommendedforyou",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": true,
|
||||
"ShowTokenStore": true,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": true,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test With Recommended For You",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ButtonDisplayOrder",
|
||||
"ShowRRPlusButton",
|
||||
"ShowRecommendedForYouButton",
|
||||
"ShowTokenStore",
|
||||
"ShowCustomClothingButton"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"jIKNxa9t8bu1FoyIcWkw3HG3P1SWdBPVVYWJ+lUE2yY=": {
|
||||
"name": "jIKNxa9t8bu1FoyIcWkw3HG3P1SWdBPVVYWJ+lUE2yY=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard.AnimateFresh.Control",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome.AnimateFresh.Control",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight.AnimateFresh.Control",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"loop_shimmer_animation",
|
||||
"CuratedList_PlayHighlight",
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_CommunityBoard"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"aI8uybX2wpUOFaQ9cVbVEUyBA7wG54xp7HjOak/TsyI=": {
|
||||
"name": "aI8uybX2wpUOFaQ9cVbVEUyBA7wG54xp7HjOak/TsyI=",
|
||||
"value": {
|
||||
"CuratedList_AppNav.WidgetPanel.Profile": "Discovery.PageSource.WWPanel.Profile",
|
||||
"CuratedList_WWPanel.Profile": "Discovery.PageSource.WWPanel.Profile"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WWPanel.Profile"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Btl9tQL3ldr6f+Sr6KIh+KPhcOXiS7FSxexqiHWrLnQ=": {
|
||||
"name": "Btl9tQL3ldr6f+Sr6KIh+KPhcOXiS7FSxexqiHWrLnQ=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"target_progression",
|
||||
"orientation_version",
|
||||
"preload_scene"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"zIo1u7JpLVjPfuT7Z//44yIjpnRwfAZag4P1VVXWtEY=": {
|
||||
"name": "zIo1u7JpLVjPfuT7Z//44yIjpnRwfAZag4P1VVXWtEY=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"orientation_sub_room_names",
|
||||
"is_giftshop",
|
||||
"token_inflation_amount",
|
||||
"storefront_type",
|
||||
"upsell_amount"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1778358864",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"d/wiK4DxKGYVA711Q+k5ybP9LKCaZ93nsQXUqp61W1o=": {
|
||||
"name": "d/wiK4DxKGYVA711Q+k5ybP9LKCaZ93nsQXUqp61W1o=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"SocialCreation2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test #2",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"target_orientation_room_name",
|
||||
"orientation_version",
|
||||
"orientation_sub_room_names",
|
||||
"prefetch_asset_bundles"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1Nl9KerAaJtwZL+sy+MDmUusQGtydrIzNNrEMFJwwS4=": {
|
||||
"name": "1Nl9KerAaJtwZL+sy+MDmUusQGtydrIzNNrEMFJwwS4=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GH2XvT0VdCZkV16fxIzs+nCfDA4DXR41AktNDOudqrQ=": {
|
||||
"name": "GH2XvT0VdCZkV16fxIzs+nCfDA4DXR41AktNDOudqrQ=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreCategories",
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fwDFGVhTjguGBMs8sFjz0mB6l914gOV9uz7CqsbxLcQ=": {
|
||||
"name": "fwDFGVhTjguGBMs8sFjz0mB6l914gOV9uz7CqsbxLcQ=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"campaignIds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Rxz9nek1ttXfm91zD1poS3nrAalID7VzeL/wIY7xiCc=": {
|
||||
"name": "Rxz9nek1ttXfm91zD1poS3nrAalID7VzeL/wIY7xiCc=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ISujjcyoJCJTH1BPr5aY6S7brXej9InTAS/j3mhbeQk=": {
|
||||
"name": "ISujjcyoJCJTH1BPr5aY6S7brXej9InTAS/j3mhbeQk=",
|
||||
"value": {
|
||||
"ShowCategoriesTab": false,
|
||||
"ShowShowMeAllRoomsButton": true,
|
||||
"GroupName": "CategoryShowMeAllRooms"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "CategoryShowMeAllRooms",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"TDvAobGWrbzY9y5SPZliwzTDkjEpMH4IbIfZcFM51Ww=": {
|
||||
"name": "TDvAobGWrbzY9y5SPZliwzTDkjEpMH4IbIfZcFM51Ww=",
|
||||
"value": {
|
||||
"use_new_room_card": true,
|
||||
"use_new_club_event_card": true,
|
||||
"use_new_item_cards": true,
|
||||
"use_new_account_cards": true,
|
||||
"use_vnext_db_room_carousels": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New Room Cards",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_new_room_card"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"IvDCGOwFclP6tkLHwVC1K9yzLSitabvOEjxFu6Jx75Q=": {
|
||||
"name": "IvDCGOwFclP6tkLHwVC1K9yzLSitabvOEjxFu6Jx75Q=",
|
||||
"value": {
|
||||
"remove_menu_finger": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BH34Hwsii153DDsjIzGfzfqaWDLw++qDDpcygNybiig=": {
|
||||
"name": "BH34Hwsii153DDsjIzGfzfqaWDLw++qDDpcygNybiig=",
|
||||
"value": {
|
||||
"use_infinite_list": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_infinite_list"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"8dxOYS2h44UwNMLcaoCmza+nmBJURUo4W/hn3uQe6mA=": {
|
||||
"name": "8dxOYS2h44UwNMLcaoCmza+nmBJURUo4W/hn3uQe6mA=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"KbYpcnBVahtSG0dtyvjebqtyK2akDmU255hrKlflkzE=": {
|
||||
"name": "KbYpcnBVahtSG0dtyvjebqtyK2akDmU255hrKlflkzE=",
|
||||
"value": {
|
||||
"show_ccu_count": false,
|
||||
"show_popular_badge": false,
|
||||
"pin_popular_room": false,
|
||||
"supported_carousels": [],
|
||||
"Discovery.CCUIconThresholds": "[ { \"Count\": 50, \"Label\": \"Hot\", \"Color\": \"#EA6F48\" } ]",
|
||||
"Discovery.PopularBadgeCCUThreshold": 100,
|
||||
"best_popular_card_threshold": -1,
|
||||
"room_list_join_or_create_count": 0,
|
||||
"room_list_join_only_count": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_ccu_count",
|
||||
"show_popular_badge",
|
||||
"pin_popular_room",
|
||||
"supported_carousels",
|
||||
"Discovery.CCUIconThresholds",
|
||||
"Discovery.PopularBadgeCCUThreshold"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"tKWY/aiNKaZtc+S6HxFuaH4Bh/K/WmfMI8uWuYA3DaE=": {
|
||||
"name": "tKWY/aiNKaZtc+S6HxFuaH4Bh/K/WmfMI8uWuYA3DaE=",
|
||||
"value": {
|
||||
"UGC.ObjectModel.PushToShadowWorld": "true"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"niBbMWgexRNe7LIUbwmBX2YHzJIV/0aoHnJWMK+yMc0=": {
|
||||
"name": "niBbMWgexRNe7LIUbwmBX2YHzJIV/0aoHnJWMK+yMc0=",
|
||||
"value": {
|
||||
"slowdown_duration_ms": 0,
|
||||
"hitch_interval_ms": 0,
|
||||
"hitch_duration_ms": 0,
|
||||
"maintain_fps": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"slowdown_duration_ms"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"qdPGcxU8g0cJ3YgQhV9C3jT9JEpM9UpF2vqSPUUcyPg=": {
|
||||
"name": "qdPGcxU8g0cJ3YgQhV9C3jT9JEpM9UpF2vqSPUUcyPg=",
|
||||
"value": {
|
||||
"slowdown_duration_ms": 0,
|
||||
"hitch_interval_ms": 0,
|
||||
"hitch_duration_ms": 0,
|
||||
"maintain_fps": 0
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"hitch_interval_ms",
|
||||
"hitch_duration_ms"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"/usQ1YzadZI0GQG3tJnLnZ0aIekkJM1468o5R64f9hk=": {
|
||||
"name": "/usQ1YzadZI0GQG3tJnLnZ0aIekkJM1468o5R64f9hk=",
|
||||
"value": {
|
||||
"isEnabled": false,
|
||||
"cooldown_base": 0,
|
||||
"cooldown_scale": 0,
|
||||
"cooldown_min": 0,
|
||||
"cooldown_max": 0,
|
||||
"cooldown_max_views": 0,
|
||||
"is_enabled_chat": false,
|
||||
"is_enabled_event": false,
|
||||
"is_enabled_gift": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"cooldown_base",
|
||||
"cooldown_scale",
|
||||
"cooldown_min",
|
||||
"cooldown_max",
|
||||
"cooldown_max_views",
|
||||
"is_enabled_event"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2345755404",
|
||||
"gateValue": "false",
|
||||
"ruleID": "1RyXaibUwiHWUoNGlHTkMJ:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"b7KyVzRalif4bRu2ExD8XyVETgsZa0Gkt7NUQEFV+wg=": {
|
||||
"name": "b7KyVzRalif4bRu2ExD8XyVETgsZa0Gkt7NUQEFV+wg=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing.NewItems.Test.RemoveBadCarousels",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Remove Bad Carousels",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreClothing"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"jZ+dhsuoCGX7G0YgbaSO4uYrGYO1SzjbvCSFLskO3t4=": {
|
||||
"name": "jZ+dhsuoCGX7G0YgbaSO4uYrGYO1SzjbvCSFLskO3t4=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseAlgorithmicDisplayPriority"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Xalfi6V+HaUxGQXO3BWP+LYVsX4TeK12WIM548yZ5M0=": {
|
||||
"name": "Xalfi6V+HaUxGQXO3BWP+LYVsX4TeK12WIM548yZ5M0=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"target_orientation_room_name",
|
||||
"orientation_version"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"FErNDm9TJoKDMIBGk55oh/uE19Fnbbw5LR445pOH9bs=": {
|
||||
"name": "FErNDm9TJoKDMIBGk55oh/uE19Fnbbw5LR445pOH9bs=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"copy_room_button_text"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"1gmpUjEGtbFkVBVkb1qei7Jm1YxpEQuA31AuSeT2D+A=": {
|
||||
"name": "1gmpUjEGtbFkVBVkb1qei7Jm1YxpEQuA31AuSeT2D+A=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreClothing",
|
||||
"PageSourcesToReorder"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"iVFFkjuDS2ls/KCmWW2nLhDgKAY7whpw5QQ5yxXz4fU=": {
|
||||
"name": "iVFFkjuDS2ls/KCmWW2nLhDgKAY7whpw5QQ5yxXz4fU=",
|
||||
"value": {
|
||||
"show_playaction_button": false,
|
||||
"default_instance_type": "",
|
||||
"show_morelikethis_button": false,
|
||||
"supported_carousels": [],
|
||||
"new_rooms_threshold_days": 30,
|
||||
"hide_cheers_for_new": true,
|
||||
"hide_cheers_count": true,
|
||||
"show_cheers_data_token": false,
|
||||
"show_new_data_token": true,
|
||||
"loop_shimmer_animation": false,
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"show_publish_state": true,
|
||||
"Creator.Room.EnableRoomTokens": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_publish_state",
|
||||
"Creator.Room.EnableRoomTokens"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JKWyMs6MG+QGlcRDO25dm9zKgnxu7QEvIUN7yVYGZIQ=": {
|
||||
"name": "JKWyMs6MG+QGlcRDO25dm9zKgnxu7QEvIUN7yVYGZIQ=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": false,
|
||||
"Override_Content_Json": "{ \"ImageName\": \"9n9d4vfl1wss3noodtexibixd.png\" }"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Override_Content_Json"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"K/+Ye/siM+OGv85ADZ3nuII63F/2pcp8Wzrgu3eErGo=": {
|
||||
"name": "K/+Ye/siM+OGv85ADZ3nuII63F/2pcp8Wzrgu3eErGo=",
|
||||
"value": {
|
||||
"CuratedList_RecRoomPlusBenefits": "Discovery.PageSource.RecRoomPlusBenefits"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_RecRoomPlusBenefits"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"W4DKbHtRjpQ11jGgLfQMJiH0rPTOgsVsmedzsV6uaI4=": {
|
||||
"name": "W4DKbHtRjpQ11jGgLfQMJiH0rPTOgsVsmedzsV6uaI4=",
|
||||
"value": {
|
||||
"online_ranking_configuration_name": "2025q2_personalized_all_items",
|
||||
"use_online_ranking": true,
|
||||
"number_of_entities_to_shuffle": 18,
|
||||
"use_contextual_features": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"online_ranking_configuration_name",
|
||||
"use_online_ranking",
|
||||
"number_of_entities_to_shuffle",
|
||||
"use_contextual_features"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"wpqIwwsIMuFHbTTotgGRbaaa0D6Amd/IT2cPPFpq814=": {
|
||||
"name": "wpqIwwsIMuFHbTTotgGRbaaa0D6Amd/IT2cPPFpq814=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"f3/zc03th5AjchXGkfv6pnE0ZglVdFtUQyPyWqrbApc=": {
|
||||
"name": "f3/zc03th5AjchXGkfv6pnE0ZglVdFtUQyPyWqrbApc=",
|
||||
"value": {},
|
||||
"rule_id": "targetingGate",
|
||||
"group": "targetingGate",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3041580392",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7c4QL3WEghkDttWgDN2m2L:0.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"LUtIrtGG3Lhe7V1cgqURHydjyqIoUqDwzdMUtvlfOVg=": {
|
||||
"name": "LUtIrtGG3Lhe7V1cgqURHydjyqIoUqDwzdMUtvlfOVg=",
|
||||
"value": {},
|
||||
"rule_id": "targetingGate",
|
||||
"group": "targetingGate",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1675960891",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ToxzcXv41EHVnCJlBnXVE9QESPldgMMpqGfjPdmJNlw=": {
|
||||
"name": "ToxzcXv41EHVnCJlBnXVE9QESPldgMMpqGfjPdmJNlw=",
|
||||
"value": {
|
||||
"isEnabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"a1TJ3DSea8XpymqduYvgTGQ5e/K5KL3Ra6H7anCh+14=": {
|
||||
"name": "a1TJ3DSea8XpymqduYvgTGQ5e/K5KL3Ra6H7anCh+14=",
|
||||
"value": {
|
||||
"Tip:Title": "Use headphones!",
|
||||
"Tip:Message": "Hey! While you’re playing on mobile, use headphones and be social!",
|
||||
"Tip:RestrictToNewUsers": false,
|
||||
"Tip:Context": "0",
|
||||
"Tip:Visibility": "1",
|
||||
"Tip:AllowCycling": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"GxVozhc+Zab2Z3bC0QcanzFQMbZkMku/y26eKPx/HSg=": {
|
||||
"name": "GxVozhc+Zab2Z3bC0QcanzFQMbZkMku/y26eKPx/HSg=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "PopUpShopStoreCarouselsList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Pop Up Shop Carousels",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService",
|
||||
"WatchStoreCarouselsListPrefix",
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService",
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Fkidbha3ieJMPpE391EVbd5Mi3FYiwKKCNfRqREAKJY=": {
|
||||
"name": "Fkidbha3ieJMPpE391EVbd5Mi3FYiwKKCNfRqREAKJY=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"conditionally_spawn_if_not_max_level"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uGyJ/Qmay7l2fz+9NM2mItyAfSrH/a9vLJPZCuFiGWc=": {
|
||||
"name": "uGyJ/Qmay7l2fz+9NM2mItyAfSrH/a9vLJPZCuFiGWc=",
|
||||
"value": {
|
||||
"CompressRecNetImages": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CompressRecNetImages"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"CdIq+DJSfQz0PBHJqpIpfKNK2QzDyMCwJmLycZTpg/k=": {
|
||||
"name": "CdIq+DJSfQz0PBHJqpIpfKNK2QzDyMCwJmLycZTpg/k=",
|
||||
"value": {
|
||||
"allow_play_together": false,
|
||||
"play_together_max_rooms": 5,
|
||||
"play_together_playlist_name": "",
|
||||
"play_together_use_recents": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"allow_play_together",
|
||||
"play_together_max_rooms",
|
||||
"play_together_playlist_name",
|
||||
"play_together_use_recents"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"KEW171HV89pBWDzZrWhn7kyAvTtuSyQqhdP+9kp/lEU=": {
|
||||
"name": "KEW171HV89pBWDzZrWhn7kyAvTtuSyQqhdP+9kp/lEU=",
|
||||
"value": {
|
||||
"show_reactions": true,
|
||||
"show_emotes": true,
|
||||
"reactions": [
|
||||
"👍",
|
||||
"❤️",
|
||||
"😭:Sad",
|
||||
"👎",
|
||||
"😲:Shocked",
|
||||
"🤣:Laugh"
|
||||
],
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [],
|
||||
"show_react_gesture_tip": false,
|
||||
"fade_react_joystick": false,
|
||||
"show_react_joystick": false,
|
||||
"react_gesture_tip": "",
|
||||
"show_react_radial_joystick": false,
|
||||
"custom_local_profile": false,
|
||||
"show_profile_blocklist": false,
|
||||
"hide_old_set_badges": false,
|
||||
"Tip:Visibility": 0,
|
||||
"snowball_enabled": false,
|
||||
"snowball_feedback_sizing": false,
|
||||
"snowball_feedback_vfx": false,
|
||||
"snowball_feedback_room_chat": false,
|
||||
"snowball_feedback_notification": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "6 Reactions Plus Emotes",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_reactions",
|
||||
"show_emotes",
|
||||
"reactions"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bbeyeq3FBtAoTjNtCiED0xiYBEAihoYQcq4G0O0/Tn8=": {
|
||||
"name": "bbeyeq3FBtAoTjNtCiED0xiYBEAihoYQcq4G0O0/Tn8=",
|
||||
"value": {
|
||||
"MaxRoomCount": -999,
|
||||
"Discovery.GenreBasedMatchmaking.FreshnessParameter": 5,
|
||||
"JoinOrCreateRoomCount": 10,
|
||||
"AdditionalJoinOnlyRoomCount": 15
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Consecutive5",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Discovery.GenreBasedMatchmaking.FreshnessParameter"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"QVh2I6PkdAyjIcDaei2xLBk41LwsZQkzNhEHFfWI+nw=": {
|
||||
"name": "QVh2I6PkdAyjIcDaei2xLBk41LwsZQkzNhEHFfWI+nw=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": false,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"NoNavBar"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"+S8jZ+x6lHaJOiC60UKa3YM6oGpq41EnXpFWAfciFMc=": {
|
||||
"name": "+S8jZ+x6lHaJOiC60UKa3YM6oGpq41EnXpFWAfciFMc=",
|
||||
"value": {
|
||||
"MaxRoomCount": -999,
|
||||
"Discovery.GenreBasedMatchmaking.FreshnessParameter": 5,
|
||||
"JoinOrCreateRoomCount": 25,
|
||||
"AdditionalJoinOnlyRoomCount": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "AddJoinOrCreate",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"JoinOrCreateRoomCount",
|
||||
"AdditionalJoinOnlyRoomCount"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"dggbMBDjGuk4Z6z2yvVXuWWESoy2bafIuyUgVHy3NYI=": {
|
||||
"name": "dggbMBDjGuk4Z6z2yvVXuWWESoy2bafIuyUgVHy3NYI=",
|
||||
"value": {
|
||||
"campaignId": 123456789
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"xrAKwLaAEHsCgNlIrhsU5SLFR4kb3S7P4ACVtXvQ3Bk=": {
|
||||
"name": "xrAKwLaAEHsCgNlIrhsU5SLFR4kb3S7P4ACVtXvQ3Bk=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": true,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": true,
|
||||
"options_count_per_platform": {
|
||||
"mobile": 4,
|
||||
"pc": 4,
|
||||
"vr": 4
|
||||
},
|
||||
"AutoCompleteIndexName": "room-autocomplete_inc_popular_rooms"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "1700 AC suggestion candidates",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_autocomplete",
|
||||
"options_count_per_platform",
|
||||
"AutoCompleteIndexName"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"b2dSKVS2jjorZgzk1MduWzANiHgbkSb/lwBTZmUJeqI=": {
|
||||
"name": "b2dSKVS2jjorZgzk1MduWzANiHgbkSb/lwBTZmUJeqI=",
|
||||
"value": {
|
||||
"voice_call_survey_canned_options": [
|
||||
"Couldn't Hear Others",
|
||||
"Others Couldn't Hear Me",
|
||||
"Voice Glitchy/Distorted",
|
||||
"Voices Cut Out/Break Up",
|
||||
"Voices Delayed/Lagged",
|
||||
"Voices Echo",
|
||||
"Other"
|
||||
],
|
||||
"room_gain_threshold": 0.55,
|
||||
"call_gain_threshold": 0.5,
|
||||
"mutually_exclusive_voice_source": true,
|
||||
"show_survey_when_leaving_call": true,
|
||||
"dynamic_switching_if_private": false,
|
||||
"current_group_name": "Show Survey",
|
||||
"survey_send_game_logs_bug_report": true,
|
||||
"voice_call_volume_percentage_default": 1,
|
||||
"show_voice_call_volume_slider": true,
|
||||
"should_deprecate_ugc_voice_channel": true,
|
||||
"Retention.IndividualVoiceVolume.Enabled": "",
|
||||
"is_individual_voice_volume_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_individual_voice_volume_enabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sBMz5JW6Iq94Uzp/hyLfKmrbjBzN6ucWu4+mbUNT9Pg=": {
|
||||
"name": "sBMz5JW6Iq94Uzp/hyLfKmrbjBzN6ucWu4+mbUNT9Pg=",
|
||||
"value": {
|
||||
"use_app_nav": false,
|
||||
"prevent_watch_home": false,
|
||||
"CuratedList_AppNavWidgetPanel": "Discovery.PageSource.AppNavWidgetPanel",
|
||||
"use_app_nav_forship": false,
|
||||
"enable_app_nav_tutorial": false,
|
||||
"enable_retain_ui_state": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_AppNavWidgetPanel",
|
||||
"use_app_nav_forship",
|
||||
"enable_app_nav_tutorial"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Yn0YLpXK2qH8U6IiSHCVZ/4NiUcxsHl+GrTcHdhXugU=": {
|
||||
"name": "Yn0YLpXK2qH8U6IiSHCVZ/4NiUcxsHl+GrTcHdhXugU=",
|
||||
"value": {
|
||||
"scalability_enabled": true,
|
||||
"scalability_preset": -1,
|
||||
"scalability_low_power": false,
|
||||
"scalability_scalemode": 4,
|
||||
"scalability_avatarenable": true,
|
||||
"scalability_textureenable": false,
|
||||
"scalability_shapesenable": true,
|
||||
"scalability_target_num_batches": 0,
|
||||
"scalability_max_triangles": 2000,
|
||||
"scalability_shapes_mem_delta": 400,
|
||||
"scalability_r1_allowed": -1,
|
||||
"scalability_r2_allowed": -1
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"scalability_preset",
|
||||
"scalability_enabled",
|
||||
"scalability_avatarenable",
|
||||
"scalability_shapesenable",
|
||||
"scalability_textureenable",
|
||||
"scalability_max_triangles",
|
||||
"scalability_shapes_mem_delta"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"wlnTEU4NECpYRO5nKad2Jr7Lv9Ost+OYSLOAynYmy3A=": {
|
||||
"name": "wlnTEU4NECpYRO5nKad2Jr7Lv9Ost+OYSLOAynYmy3A=",
|
||||
"value": {
|
||||
"manipulate_enabled": true,
|
||||
"manipulate_collision_detection_enabled": true,
|
||||
"manipulate_touch_rot_degrees_per_inch": 120,
|
||||
"manipulate_toggle_enabled": true,
|
||||
"manipulate_quick_manipulate_enabled": false,
|
||||
"manipulate_disable_hand_collisions": false,
|
||||
"manipulate_cylindrical_placement_enabled": true,
|
||||
"manipulate_collision_outline_enabled": false,
|
||||
"manipulate_mp_surface_snap": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"manipulate_toggle_enabled"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"G6ujBT3DZxgAMwCB1ZwWwYWUwvZUyBXnKArN1SWYJ3E=": {
|
||||
"name": "G6ujBT3DZxgAMwCB1ZwWwYWUwvZUyBXnKArN1SWYJ3E=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_store_button"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"O4o4nERamhYtrGiUqb76pabqhjHA5g1AO/3L69ys4GA=": {
|
||||
"name": "O4o4nERamhYtrGiUqb76pabqhjHA5g1AO/3L69ys4GA=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"m76Z+bzx/AnEdT6kTf0up9skwASxgz34pqmfpr+owSU=": {
|
||||
"name": "m76Z+bzx/AnEdT6kTf0up9skwASxgz34pqmfpr+owSU=",
|
||||
"value": {
|
||||
"heartbeat_interval": 15,
|
||||
"force_heartbeat_on_update": true,
|
||||
"enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"SwUyl/H0x8nV/9HDZlxmjr6ZW2B1+SZ+Yxpvd5J8yWA=": {
|
||||
"name": "SwUyl/H0x8nV/9HDZlxmjr6ZW2B1+SZ+Yxpvd5J8yWA=",
|
||||
"value": {},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"+H0mlxbwmQrdmAXoSrP13tr6ybNXC8FHD8JA2mC/gPI=": {
|
||||
"name": "+H0mlxbwmQrdmAXoSrP13tr6ybNXC8FHD8JA2mC/gPI=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"GroupName",
|
||||
"PlayMenuCarouselOrdering_1",
|
||||
"remove_chips",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"hhHGSaybgblfuQKeVQwPHKIMrhevWcaUw0Y4Uew6GAI=": {
|
||||
"name": "hhHGSaybgblfuQKeVQwPHKIMrhevWcaUw0Y4Uew6GAI=",
|
||||
"value": {
|
||||
"enable_discoverability": false,
|
||||
"enable_party_private_instance": false,
|
||||
"Lifecycle.PSF.EnableAllChatVoiceCalls": false,
|
||||
"Tip:Visibility": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_discoverability",
|
||||
"Tip:Visibility"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "930626607",
|
||||
"gateValue": "true",
|
||||
"ruleID": "4gQtmddP2Gauv81AqWUi7a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gF9vttI8GGyyORDAykK0R+y3drS1jhcvVsSnGu1qlL8=": {
|
||||
"name": "gF9vttI8GGyyORDAykK0R+y3drS1jhcvVsSnGu1qlL8=",
|
||||
"value": {
|
||||
"manipulate_tool_r2_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"G3ur0gRv3ZO3A13ihop1fQvDwFPswZ0xIIitoN4QTww=": {
|
||||
"name": "G3ur0gRv3ZO3A13ihop1fQvDwFPswZ0xIIitoN4QTww=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Store at Spot 4",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"dorm_mirror_button_display_order",
|
||||
"show_store_button",
|
||||
"shrink_buttons"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"OVXbbmrbH1Yn0Q2Z6WXi+fxYGv3MIGXfZ0gKDdiiIF4=": {
|
||||
"name": "OVXbbmrbH1Yn0Q2Z6WXi+fxYGv3MIGXfZ0gKDdiiIF4=",
|
||||
"value": {
|
||||
"rrui_create_invention_use_rrui_menu": false,
|
||||
"rrui_create_invention_tool_deprecating_title": "",
|
||||
"rrui_create_invention_tool_depricating_description": "",
|
||||
"rrui_invention_details_use_rrui_menu": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"rrui_invention_details_use_rrui_menu"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"a5FQVRjWaS9e9NCBYE31b/7HH9xtUFrDcKlggXky0ds=": {
|
||||
"name": "a5FQVRjWaS9e9NCBYE31b/7HH9xtUFrDcKlggXky0ds=",
|
||||
"value": {
|
||||
"IsCustomDormForNewUsersEnabled": false,
|
||||
"IsCustomDormCustomizationLocked": false,
|
||||
"ShowNewTutorial": false,
|
||||
"ShowTutorialOnSpawn": false,
|
||||
"IsDormSlotsVisible": true,
|
||||
"ShowDiscoveryNotification": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"IsCustomDormForNewUsersEnabled",
|
||||
"IsDormSlotsVisible",
|
||||
"ShowDiscoveryNotification"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"4GekHEW2QTPbQWQz6yPdPwXKx95oC8fV5eeyYLeJpOQ=": {
|
||||
"name": "4GekHEW2QTPbQWQz6yPdPwXKx95oC8fV5eeyYLeJpOQ=",
|
||||
"value": {
|
||||
"ReduceAvataritemCalls": false,
|
||||
"ReduceStorefrontCalls": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ReduceAvataritemCalls",
|
||||
"ReduceStorefrontCalls"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Rji2QALBYCOgwQoL/Hm7hSc7RrP7teL6WWwo0jGgLYU=": {
|
||||
"name": "Rji2QALBYCOgwQoL/Hm7hSc7RrP7teL6WWwo0jGgLYU=",
|
||||
"value": {
|
||||
"isEnabled": false,
|
||||
"cooldown_base": 86400,
|
||||
"cooldown_scale": 0,
|
||||
"cooldown_min": 86400,
|
||||
"cooldown_max": 604800,
|
||||
"cooldown_max_views": -1,
|
||||
"is_enabled_chat": true,
|
||||
"is_enabled_event": false,
|
||||
"is_enabled_gift": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"cooldown_base",
|
||||
"cooldown_scale",
|
||||
"cooldown_min",
|
||||
"cooldown_max",
|
||||
"cooldown_max_views",
|
||||
"is_enabled_chat"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2345755404",
|
||||
"gateValue": "false",
|
||||
"ruleID": "1RyXaibUwiHWUoNGlHTkMJ:5.00:1"
|
||||
},
|
||||
{
|
||||
"gate": "930626607",
|
||||
"gateValue": "true",
|
||||
"ruleID": "4gQtmddP2Gauv81AqWUi7a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"H2K/NcbXaOpT7rLMSKyHmJ1s87zl5aUHwZAs1wMkXFc=": {
|
||||
"name": "H2K/NcbXaOpT7rLMSKyHmJ1s87zl5aUHwZAs1wMkXFc=",
|
||||
"value": {
|
||||
"boot_strategy": 2
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test #2",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"boot_strategy"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Da6Q9K1O5LOiDxLRzM7GWenPN6ByK0hMcu7p2KDuYQw=": {
|
||||
"name": "Da6Q9K1O5LOiDxLRzM7GWenPN6ByK0hMcu7p2KDuYQw=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_look_outfit_seperation_toast"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"y69G17YHuqaUiPBrciw8c7M+NLZIMdHL1LfTBsteWfg=": {
|
||||
"name": "y69G17YHuqaUiPBrciw8c7M+NLZIMdHL1LfTBsteWfg=",
|
||||
"value": {
|
||||
"IsSearchWithTagsEnabled": true,
|
||||
"UseElasticsearch": true,
|
||||
"UseSecondaryOrdering": true,
|
||||
"UseFuzzySearch": true,
|
||||
"SearchDescription": false,
|
||||
"NameRelevanceBoost": 1,
|
||||
"TagRelevanceBoost": 1,
|
||||
"DescriptionRelevanceBoost": 1
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Elasticsearch",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UseElasticsearch"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"T+68iwS8v8uvsndh02VxwH8iF/Lgj7k8tct4Jfw+m4E=": {
|
||||
"name": "T+68iwS8v8uvsndh02VxwH8iF/Lgj7k8tct4Jfw+m4E=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"bKd3TqsQzsp3W6wSxg9XxvpwXbOnLd1eamM2nOrzKyA=": {
|
||||
"name": "bKd3TqsQzsp3W6wSxg9XxvpwXbOnLd1eamM2nOrzKyA=",
|
||||
"value": {
|
||||
"use_item_rrp_upsell": true,
|
||||
"show_for_save": false,
|
||||
"show_for_publish": false,
|
||||
"RecRoomPlusSignUpBonusIds": [
|
||||
3
|
||||
]
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_item_rrp_upsell"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bVT8ylzgH72QdGgYAAuE0TmedbzY19YIUTVirc8p7Xg=": {
|
||||
"name": "bVT8ylzgH72QdGgYAAuE0TmedbzY19YIUTVirc8p7Xg=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"oEUIAee3SnooR91A81fzA9cmIlHlFXoi/dsApjVoMCs=": {
|
||||
"name": "oEUIAee3SnooR91A81fzA9cmIlHlFXoi/dsApjVoMCs=",
|
||||
"value": {
|
||||
"CuratedList_AppNav.WidgetPanel.Profile": "Discovery.PageSource.WWPanel.Profile",
|
||||
"CuratedList_WWPanel.Profile": "Discovery.PageSource.WWPanel.Profile"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WWPanel.Profile"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"dZPYuroC2W1FNnzZqUBJmdHsVwcORPmd1/GqVXQPnQU=": {
|
||||
"name": "dZPYuroC2W1FNnzZqUBJmdHsVwcORPmd1/GqVXQPnQU=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"campaignIds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Qw9xQNSgfpvZU9kjhfV3xNlmGzu9veSu258xpWoxqNk=": {
|
||||
"name": "Qw9xQNSgfpvZU9kjhfV3xNlmGzu9veSu258xpWoxqNk=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Econ.EnableTryingBundleItems"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"HbLtF9xolIe0rvOALjzqqMl2gk4k+dLVqCNZ6oQ2Cmo=": {
|
||||
"name": "HbLtF9xolIe0rvOALjzqqMl2gk4k+dLVqCNZ6oQ2Cmo=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "New Store Item Details",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"AvatarPreviewSize",
|
||||
"UseVNextItemDetailPages",
|
||||
"use_buy_cta_button",
|
||||
"hide_rarity",
|
||||
"store_item_prefab_variant_name",
|
||||
"hide_prediscount_price"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"UHk3Xf5x5v9OV0Uk1qjbzsvHf/toUwNxaQNfWbbOn5Y=": {
|
||||
"name": "UHk3Xf5x5v9OV0Uk1qjbzsvHf/toUwNxaQNfWbbOn5Y=",
|
||||
"value": {
|
||||
"enabled": true,
|
||||
"previewTimeSec": 0.4
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1926385031",
|
||||
"gateValue": "true",
|
||||
"ruleID": "1VpLvSsppJityYlbf2J0ms"
|
||||
}
|
||||
]
|
||||
},
|
||||
"T6cWS0l2K7SY+tKApOeu0yLNtw+W3n3+NVS48o+Cm1Y=": {
|
||||
"name": "T6cWS0l2K7SY+tKApOeu0yLNtw+W3n3+NVS48o+Cm1Y=",
|
||||
"value": {
|
||||
"scalability_enabled": true,
|
||||
"scalability_preset": -1,
|
||||
"scalability_low_power": false,
|
||||
"scalability_scalemode": 4,
|
||||
"scalability_avatarenable": true,
|
||||
"scalability_textureenable": false,
|
||||
"scalability_shapesenable": true,
|
||||
"scalability_target_num_batches": 0,
|
||||
"scalability_max_triangles": 2000,
|
||||
"scalability_shapes_mem_delta": 400,
|
||||
"scalability_r1_allowed": -1,
|
||||
"scalability_r2_allowed": -1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"scalability_enabled",
|
||||
"scalability_avatarenable",
|
||||
"scalability_textureenable",
|
||||
"scalability_shapesenable",
|
||||
"scalability_preset",
|
||||
"scalability_r2_allowed",
|
||||
"scalability_r1_allowed"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ogs8H2Wh+QFHqohly8JgyNRRcoqflUEIE9s7L+pEF1U=": {
|
||||
"name": "ogs8H2Wh+QFHqohly8JgyNRRcoqflUEIE9s7L+pEF1U=",
|
||||
"value": {
|
||||
"is_enabled": true,
|
||||
"notice_on_visit_probability": 0.5,
|
||||
"notice_duration_seconds": 2,
|
||||
"message_string": "Voice moderation is active in this room.",
|
||||
"title_string": "Coach is listening!",
|
||||
"moderated_player_only": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Moderated Player - Short Duration",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_enabled",
|
||||
"notice_on_visit_probability",
|
||||
"notice_duration_seconds",
|
||||
"moderated_player_only"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"jugmSGVQhEBCG0HCyns5ZVcZnoZNR//4CSB1oIBngJ8=": {
|
||||
"name": "jugmSGVQhEBCG0HCyns5ZVcZnoZNR//4CSB1oIBngJ8=",
|
||||
"value": {
|
||||
"voice_call_survey_canned_options": [
|
||||
"Couldn't Hear Others",
|
||||
"Others Couldn't Hear Me",
|
||||
"Voice Glitchy/Distorted",
|
||||
"Voices Cut Out/Break Up",
|
||||
"Voices Delayed/Lagged",
|
||||
"Voices Echo",
|
||||
"Other"
|
||||
],
|
||||
"room_gain_threshold": 0.55,
|
||||
"call_gain_threshold": 0.5,
|
||||
"mutually_exclusive_voice_source": true,
|
||||
"show_survey_when_leaving_call": true,
|
||||
"dynamic_switching_if_private": false,
|
||||
"current_group_name": "Show Survey",
|
||||
"survey_send_game_logs_bug_report": true,
|
||||
"voice_call_volume_percentage_default": 1,
|
||||
"show_voice_call_volume_slider": true,
|
||||
"should_deprecate_ugc_voice_channel": true,
|
||||
"Retention.IndividualVoiceVolume.Enabled": "",
|
||||
"is_individual_voice_volume_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"should_deprecate_ugc_voice_channel"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"u00s6p6GAT2/AuJYnNj/gtYvXdmMgc8fTQ+66JLvxsw=": {
|
||||
"name": "u00s6p6GAT2/AuJYnNj/gtYvXdmMgc8fTQ+66JLvxsw=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreFeatured"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"JD+7wI41/Wf5VfUYu7zEnDUIjz4aeDTpIz22tiUo7ns=": {
|
||||
"name": "JD+7wI41/Wf5VfUYu7zEnDUIjz4aeDTpIz22tiUo7ns=",
|
||||
"value": {
|
||||
"CuratedList_PlayCategories": "Discovery.PageSource.PlayCategories"
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_PlayCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"e9mR+ZAh5GTaM5aZ9cuXkk69ySjyRq9LuBnFaOivmrY=": {
|
||||
"name": "e9mR+ZAh5GTaM5aZ9cuXkk69ySjyRq9LuBnFaOivmrY=",
|
||||
"value": {
|
||||
"canned_chat_RecRoomDefault": [
|
||||
"Hi!",
|
||||
"Bye!",
|
||||
"Ready?",
|
||||
"BRB",
|
||||
"Let’s go!"
|
||||
],
|
||||
"canned_chat_RoomDefault": [
|
||||
"Come with me",
|
||||
"Help!",
|
||||
"Let's party up!",
|
||||
"Let's be friends!",
|
||||
"May I pick the room?"
|
||||
],
|
||||
"canned_chat_CustomA": [
|
||||
"Yes",
|
||||
"OK",
|
||||
"Thanks",
|
||||
"LOL",
|
||||
"You look great!"
|
||||
],
|
||||
"canned_chat_CustomB": [
|
||||
"No",
|
||||
"Boo",
|
||||
"Sorry",
|
||||
"I don’t like this",
|
||||
"What the coach?!"
|
||||
],
|
||||
"room_chat_bubble_use_revamped_ui": true,
|
||||
"room_chat_bubble_font_size_mult": 1.5,
|
||||
"room_chat_bubble_bg_color": "#ffffffaa",
|
||||
"room_chat_bubble_text_color": "black",
|
||||
"room_chat_bubble_max_emoji_embiggen_len": 1,
|
||||
"room_chat_bubble_embiggen_mult": 2,
|
||||
"room_chat_bubble_should_embolden": true,
|
||||
"room_chat_bubble_text_alpha": [
|
||||
1,
|
||||
0.8,
|
||||
0.6
|
||||
],
|
||||
"room_chat_bubble_bg_alpha": [
|
||||
0.66,
|
||||
0.5,
|
||||
0.3
|
||||
],
|
||||
"room_chat_canned_detached_mode": "right",
|
||||
"show_party_chat_in_room_chat_menu": true,
|
||||
"enable_party_voice_toggle_in_room_chat": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"enable_party_voice_toggle_in_room_chat"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9rEceJ3nC8JmUPE9QUJf7TzKnj5qOqsbyFrIGgiQb/I=": {
|
||||
"name": "9rEceJ3nC8JmUPE9QUJf7TzKnj5qOqsbyFrIGgiQb/I=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreClothing"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"KmZ0sW8xofqoeKwofqlwwvC/k8s62BWIQx+amsUXRHo=": {
|
||||
"name": "KmZ0sW8xofqoeKwofqlwwvC/k8s62BWIQx+amsUXRHo=",
|
||||
"value": {
|
||||
"place_r2_support_enabled": true,
|
||||
"place_improved_rotation_enabled": true,
|
||||
"place_touch_depth_enabled": true,
|
||||
"place_touch_tutorial_enabled": true,
|
||||
"place_max_tool_distance": 3,
|
||||
"place_improved_large_objects_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"place_improved_large_objects_enabled"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GPrfS7IgAzkmXoPAQymWAV59Wh+86dv9cJJgllma0pM=": {
|
||||
"name": "GPrfS7IgAzkmXoPAQymWAV59Wh+86dv9cJJgllma0pM=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreCategories"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"+Vr/LSU7nl00z9WkSQAgo20BTUVf8lbsMukbwCaKcnM=": {
|
||||
"name": "+Vr/LSU7nl00z9WkSQAgo20BTUVf8lbsMukbwCaKcnM=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreCategories"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"rfTphgkbexd18XFoJYCMlEs5BOQn2HMig3W5NvtHNWY=": {
|
||||
"name": "rfTphgkbexd18XFoJYCMlEs5BOQn2HMig3W5NvtHNWY=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_StoreAccessories",
|
||||
"CuratedList_StoreAppearance",
|
||||
"CuratedList_StoreCategories",
|
||||
"CuratedList_StoreClothing",
|
||||
"CuratedList_StoreConsumables",
|
||||
"CuratedList_StoreFeatured",
|
||||
"CuratedList_StoreInventions",
|
||||
"CuratedList_StoreOrientation",
|
||||
"CuratedList_StoreStyling"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"TcM5jeRnEpsrAxTyDdnGtE9HElWeJIKp0x9RU2BUs7w=": {
|
||||
"name": "TcM5jeRnEpsrAxTyDdnGtE9HElWeJIKp0x9RU2BUs7w=",
|
||||
"value": {
|
||||
"Tip:Title": "Emoji in your Nametag!",
|
||||
"Tip:Message": "Rec Room Plus subscribers can now choose an Emoji to display in their Nametag!",
|
||||
"Tip:RestrictToNewUsers": false,
|
||||
"Tip:Context": 0,
|
||||
"Tip:Visibility": 0,
|
||||
"Tip:AllowCycling": true,
|
||||
"Tip:PlatformMask": -1,
|
||||
"Tip:InputType": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"BUVeb2jyzgoE3fWeOgu8G4ore/GABmmIqfUAeggKaHU=": {
|
||||
"name": "BUVeb2jyzgoE3fWeOgu8G4ore/GABmmIqfUAeggKaHU=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"depth_multiplier"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"t/ucRKSoYmmtWB9c/V1ztsbcncfNYMUVlKS8/LJRCTo=": {
|
||||
"name": "t/ucRKSoYmmtWB9c/V1ztsbcncfNYMUVlKS8/LJRCTo=",
|
||||
"value": {
|
||||
"card_size": "large",
|
||||
"hidden_types": [],
|
||||
"display_order": [],
|
||||
"display order": {},
|
||||
"isPromptingLowestTokenPackage": false,
|
||||
"prompt_lowest_token_package": false,
|
||||
"only_prompt_non_payer": true,
|
||||
"hide_token_store": false,
|
||||
"hide_buy_token_and_items": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"card_size",
|
||||
"hidden_types",
|
||||
"display_order",
|
||||
"prompt_lowest_token_package"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"OwY5MRJZttuGWroZMx/p6UoT977tG5oZxiwAq3taHTQ=": {
|
||||
"name": "OwY5MRJZttuGWroZMx/p6UoT977tG5oZxiwAq3taHTQ=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"partial_fallback_fonts"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"DzCHNKa39nI/zNEF5xNx4wL+xIxi60ZwS7DjH7LkHH4=": {
|
||||
"name": "DzCHNKa39nI/zNEF5xNx4wL+xIxi60ZwS7DjH7LkHH4=",
|
||||
"value": {
|
||||
"load_backpack_tools_on_demand": true,
|
||||
"load_room_chat_preview_on_demand": false,
|
||||
"load_room_chat_expanded_on_demand": true,
|
||||
"split_room_chat_by_vr_and_non_vr": true,
|
||||
"only_load_in_vr": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"load_backpack_tools_on_demand"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"PQE6HBQCR5oNOW/4EsjrVMc1wGm1vW4i/6yoH1ReRho=": {
|
||||
"name": "PQE6HBQCR5oNOW/4EsjrVMc1wGm1vW4i/6yoH1ReRho=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"Discovery.UseNewDiscoveryServerAPI",
|
||||
"WatchHomeCarouselOrdering",
|
||||
"PlayMenuCarouselOrdering_1"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"dacuqZfSTlSxkgqQ2q4yoqRRqxiVI9iqnwegxa4E20Q=": {
|
||||
"name": "dacuqZfSTlSxkgqQ2q4yoqRRqxiVI9iqnwegxa4E20Q=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test - Full size",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"reduce_imposter_size_divisor"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"VPIvYBXdU0j3x3vp/LBTN0FrRALymXJIGUj+kLUn4JM=": {
|
||||
"name": "VPIvYBXdU0j3x3vp/LBTN0FrRALymXJIGUj+kLUn4JM=",
|
||||
"value": {
|
||||
"removePortraitMode": false,
|
||||
"recnet_deserialize_json_on_background_thread_length_threshold": 1000,
|
||||
"recnet_use_besthttp_request_queue": false,
|
||||
"parallelize_photon_connection": true,
|
||||
"start_critical_operations_early": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": true,
|
||||
"id_type": "stableID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"recnet_use_besthttp_request_queue"
|
||||
],
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "795959371",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7zLAnzZ4AiAvCAgX6xB3ZL:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Co4cUdpH5exUyX1G76Yb9cIFyb+m4tn9POqQ3yA/xLc=": {
|
||||
"name": "Co4cUdpH5exUyX1G76Yb9cIFyb+m4tn9POqQ3yA/xLc=",
|
||||
"value": {
|
||||
"third_person_button_variant": "LARGE_LIVE_TQ",
|
||||
"third_person_button_icon": "EYE",
|
||||
"third_person_button_layout": "LARGE_RADIAL"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Radial Eye",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"third_person_button_icon",
|
||||
"third_person_button_layout"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"g13ZTrYA+ON2NyZf8qpT1MUsmy8nj4Uvvm6Mq9tk80I=": {
|
||||
"name": "g13ZTrYA+ON2NyZf8qpT1MUsmy8nj4Uvvm6Mq9tk80I=",
|
||||
"value": {
|
||||
"use_vnext_profile_remote": true,
|
||||
"use_vnext_profile_local": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"fzMklC0+oMViPmoajm95HjXmXMpuzPv3VXsN0Xyms0s=": {
|
||||
"name": "fzMklC0+oMViPmoajm95HjXmXMpuzPv3VXsN0Xyms0s=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"use_categorization_improvements_store"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"WnCqREQvhchsYExdU134TtvzRSMIZu3ckhBApIIU9EQ=": {
|
||||
"name": "WnCqREQvhchsYExdU134TtvzRSMIZu3ckhBApIIU9EQ=",
|
||||
"value": {
|
||||
"auto_connect_enabled": false,
|
||||
"recommendations_enabled": false,
|
||||
"drag_and_release_enabled": true,
|
||||
"smart_circuit_enabled": true,
|
||||
"spawn_time_threshold_seconds": 0.5,
|
||||
"spawn_indicator_threshold_seconds": 0.3,
|
||||
"spawn_distance_threshold": 0.005,
|
||||
"dismiss_distance_threshold": 0.5,
|
||||
"max_allowed_dialog_distance": 1,
|
||||
"max_dialog_alignment_distance": 2.5,
|
||||
"max_recommendations_per_port": 50,
|
||||
"ideal_recommendations_no": 25,
|
||||
"backfill_recommendation_results": false
|
||||
},
|
||||
"rule_id": "abandoned",
|
||||
"group": "abandoned",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"auto_connect_enabled",
|
||||
"drag_and_release_enabled",
|
||||
"recommendations_enabled",
|
||||
"smart_circuit_enabled",
|
||||
"spawn_time_threshold_seconds",
|
||||
"spawn_indicator_threshold_seconds",
|
||||
"spawn_distance_threshold",
|
||||
"dismiss_distance_threshold",
|
||||
"max_allowed_dialog_distance",
|
||||
"max_dialog_alignment_distance",
|
||||
"ideal_recommendations_no",
|
||||
"max_recommendations_per_port",
|
||||
"backfill_recommendation_results"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"3lVAj4q8GdlIjYAimsuG1KrAcAU88HfPortXdj9Rac4=": {
|
||||
"name": "3lVAj4q8GdlIjYAimsuG1KrAcAU88HfPortXdj9Rac4=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": true,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control ",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"store_item_prefab_variant_name",
|
||||
"hide_rarity",
|
||||
"use_buy_cta_button",
|
||||
"hide_prediscount_price"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7fRveVbHQVzpO+KrAYIsUAGIKE6eNUXiRmz3QNuvjVo=": {
|
||||
"name": "7fRveVbHQVzpO+KrAYIsUAGIKE6eNUXiRmz3QNuvjVo=",
|
||||
"value": {
|
||||
"show_ccu_count": false,
|
||||
"show_popular_badge": true,
|
||||
"pin_popular_room": false,
|
||||
"supported_carousels": [
|
||||
"SEARCH_ROOMS_WATCH"
|
||||
],
|
||||
"Discovery.CCUIconThresholds": "[ { \"Count\": 100, \"Label\": \"Hot\", \"Color\": \"#EA6F48\" } ]",
|
||||
"Discovery.PopularBadgeCCUThreshold": 100,
|
||||
"best_popular_card_threshold": -1,
|
||||
"room_list_join_or_create_count": 0,
|
||||
"room_list_join_only_count": 0
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"supported_carousels",
|
||||
"show_ccu_count",
|
||||
"show_popular_badge",
|
||||
"Discovery.PopularBadgeCCUThreshold",
|
||||
"Discovery.CCUIconThresholds"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"Ktq+byUHMUeEzEgm/B4QDkkGHJo5gtljS/VEoJV6P3Y=": {
|
||||
"name": "Ktq+byUHMUeEzEgm/B4QDkkGHJo5gtljS/VEoJV6P3Y=",
|
||||
"value": {
|
||||
"Override": "GLES"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"lyuGTVpOkhcUJBgm/W8H7QLprHYFJaECLRn0vStyLs4=": {
|
||||
"name": "lyuGTVpOkhcUJBgm/W8H7QLprHYFJaECLRn0vStyLs4=",
|
||||
"value": {
|
||||
"voice_call_survey_canned_options": [
|
||||
"Couldn't Hear Others",
|
||||
"Others Couldn't Hear Me",
|
||||
"Voice Glitchy/Distorted",
|
||||
"Voices Cut Out/Break Up",
|
||||
"Voices Delayed/Lagged",
|
||||
"Voices Echo",
|
||||
"Other"
|
||||
],
|
||||
"room_gain_threshold": 0.55,
|
||||
"call_gain_threshold": 0.5,
|
||||
"mutually_exclusive_voice_source": true,
|
||||
"show_survey_when_leaving_call": true,
|
||||
"dynamic_switching_if_private": false,
|
||||
"current_group_name": "Show Survey",
|
||||
"survey_send_game_logs_bug_report": true,
|
||||
"voice_call_volume_percentage_default": 1,
|
||||
"show_voice_call_volume_slider": true,
|
||||
"should_deprecate_ugc_voice_channel": true,
|
||||
"Retention.IndividualVoiceVolume.Enabled": "",
|
||||
"is_individual_voice_volume_enabled": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Show Survey",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"show_survey_when_leaving_call",
|
||||
"current_group_name",
|
||||
"survey_send_game_logs_bug_report"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"7qxjoOBfWak5lBwUdErFF3VmyKz17aBpoQOS+ienVUM=": {
|
||||
"name": "7qxjoOBfWak5lBwUdErFF3VmyKz17aBpoQOS+ienVUM=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WatchHome",
|
||||
"CuratedList_PlayHighlight"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"c/V8hl1vSVhQPsl3ndDttcXl9Vi6wX9bgXu/pZIdcWw=": {
|
||||
"name": "c/V8hl1vSVhQPsl3ndDttcXl9Vi6wX9bgXu/pZIdcWw=",
|
||||
"value": {
|
||||
"UGC.Crowd.FitScoreMaximum": "0"
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Control",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"UGC.Crowd.FitScoreMaximum"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"ITfBfanGs963ySTQNsMu73dgDg5T6EXnODOKJpC8sEg=": {
|
||||
"name": "ITfBfanGs963ySTQNsMu73dgDg5T6EXnODOKJpC8sEg=",
|
||||
"value": {
|
||||
"algo_name": "Rooms_MiniLM_L6_v2",
|
||||
"algo_version": "1"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"algo_name",
|
||||
"algo_version"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"6rJdUFuTwdl/dRrMO3DhF5WAKj0mciubxJnBs5ubjk4=": {
|
||||
"name": "6rJdUFuTwdl/dRrMO3DhF5WAKj0mciubxJnBs5ubjk4=",
|
||||
"value": {
|
||||
"DefaultUnlockedValueThreshold": 3,
|
||||
"DailyTokenCapOverride": 6217911,
|
||||
"MaximumPostActivityRewardCountOverride": 10,
|
||||
"GiftContextOverride": "GameRewards",
|
||||
"RewardProbabilityOverride": 1,
|
||||
"TokenGiftDrop:Common": 3397,
|
||||
"TokenGiftDrop:Common:Subscribers": 4013,
|
||||
"TokenGiftDrop:Uncommon": 3396,
|
||||
"TokenGiftDrop:Uncommon:Subscribers": 4014,
|
||||
"TokenGiftDrop:Rare": 3395,
|
||||
"TokenGiftDrop:Rare:Subscribers": 4015,
|
||||
"TokenGiftDrop:Epic": 3394,
|
||||
"TokenGiftDrop:Epic:Subscribers": 4016,
|
||||
"IsSearchWithTagsEnabled": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"MaximumPostActivityRewardCountOverride"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"oPc4r66NsshmH7q4cqJD1rWjKKZNCCgt/LJusNWS3EM=": {
|
||||
"name": "oPc4r66NsshmH7q4cqJD1rWjKKZNCCgt/LJusNWS3EM=",
|
||||
"value": {
|
||||
"online_ranking_configuration_name": "2025q2_personalized_all_items_v1_5",
|
||||
"use_online_ranking": true,
|
||||
"number_of_entities_to_shuffle": 18,
|
||||
"use_contextual_features": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test2 option 4 and score scaled",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"online_ranking_configuration_name",
|
||||
"use_online_ranking",
|
||||
"number_of_entities_to_shuffle",
|
||||
"use_contextual_features"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"k7dgdvjanEeqsf5+UpH/pQVfHcpbIRtSX9XYn3EBX4k=": {
|
||||
"name": "k7dgdvjanEeqsf5+UpH/pQVfHcpbIRtSX9XYn3EBX4k=",
|
||||
"value": {
|
||||
"ShowSearchIcon": false,
|
||||
"ShowSearchButton": false,
|
||||
"ShowSearchBar": true
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Search Bar",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ShowSearchIcon",
|
||||
"ShowSearchButton",
|
||||
"ShowSearchBar"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"liVD3hXm3X7DfLHzdwBUB04jxAuP2rMz/bjo8pJAjO8=": {
|
||||
"name": "liVD3hXm3X7DfLHzdwBUB04jxAuP2rMz/bjo8pJAjO8=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tryon",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": false,
|
||||
"ShowTokenStore": false,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": true,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"ButtonDisplayOrder",
|
||||
"ShowTryOnButton",
|
||||
"TryOnButtonText",
|
||||
"TryOnImageIndex",
|
||||
"ShowRecommendedForYouButton"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"9xTKddxspItk2YQSonTNXnl5RZRYyJjseM9YgUJvMQc=": {
|
||||
"name": "9xTKddxspItk2YQSonTNXnl5RZRYyJjseM9YgUJvMQc=",
|
||||
"value": {
|
||||
"useOptimizedSchedulerUpdateQueues": false,
|
||||
"optimizeRbexSleep": false,
|
||||
"optimizeSyncParent": true,
|
||||
"slowdown_duration_ms": 0,
|
||||
"hitch_interval_ms": 0,
|
||||
"hitch_duration_ms": 0,
|
||||
"targetFramerate": 0,
|
||||
"maintain_fps": 0,
|
||||
"smallUpdateOptimizations2": true,
|
||||
"cullOldNetworkTransformSyncData": true,
|
||||
"opt_android_reliable_ticks": true,
|
||||
"optimizeQuestUmbraCulling": true,
|
||||
"optimizeDecals": true,
|
||||
"srp_batch_support_decals": true,
|
||||
"srp_batch_support_makerpen": true,
|
||||
"srp_batch_support_avatars": true,
|
||||
"srp_batch_healthbars": true,
|
||||
"optimizeFaceCameraUIAnchorSync": true,
|
||||
"useCachedViewPostprocessingOptimizations": true,
|
||||
"enableRRStudioCanvasCulling": true,
|
||||
"optimizeVoiceLinesController2D": true,
|
||||
"optimizeDistanceFadeManager": true,
|
||||
"enableCanvasDistanceCulling": true,
|
||||
"optimizeRbexSleepKinematicCheck": true,
|
||||
"optimizeSFXAndParticleReleaseMonitoring": true,
|
||||
"max_fixedUpdates_onMobile": 0,
|
||||
"optimizeTerrain": true,
|
||||
"optimizePhysicalTextUpdate": true,
|
||||
"optimizeConnectedGrabbableDecorationColliders": false,
|
||||
"optimize_fastgraphicraycaster_visibility": true,
|
||||
"disableUIAnchorFaceCameraForMirrors": true,
|
||||
"cullRemotePlayerMovementFeedbackParticles": true,
|
||||
"optimizeScreenInputHandlerUpdateStateForVR": true,
|
||||
"enableMuzzleFlashDistanceCulling": true,
|
||||
"fixAudioDistanceChecks": false,
|
||||
"optimize_avatar_network_interpolation": true,
|
||||
"useMirrorOcclusionCulling": true,
|
||||
"useNametagNonUGUI": true,
|
||||
"forceIOSHitching": false
|
||||
},
|
||||
"rule_id": "launchedGroup",
|
||||
"group": "launchedGroup",
|
||||
"group_name": "Test",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"useNametagNonUGUI"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
},
|
||||
"V9ggE+IBccWKFj6wv8tOjhWFcKIfzGTvOE/iauI/j7E=": {
|
||||
"name": "V9ggE+IBccWKFj6wv8tOjhWFcKIfzGTvOE/iauI/j7E=",
|
||||
"value": {
|
||||
"is_daily_challenge_variations_enabled": false,
|
||||
"daily_objectives_override": {},
|
||||
"is_daily_challenge_discoverability_enabled": false,
|
||||
"is_new_ui": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"is_device_based": false,
|
||||
"id_type": "userID",
|
||||
"is_experiment_active": false,
|
||||
"is_user_in_experiment": false,
|
||||
"is_in_layer": true,
|
||||
"explicit_parameters": [
|
||||
"is_daily_challenge_variations_enabled",
|
||||
"daily_objectives_override",
|
||||
"is_new_ui"
|
||||
],
|
||||
"secondary_exposures": []
|
||||
}
|
||||
},
|
||||
"layer_configs": {
|
||||
"aAcyczmThXs5X2xKvGjVk4Fr57cDpucVUDyeBvfcwc0=": {
|
||||
"name": "aAcyczmThXs5X2xKvGjVk4Fr57cDpucVUDyeBvfcwc0=",
|
||||
"value": {
|
||||
"show_rrui_for_leave_party": true,
|
||||
"show_room_capacity_party_size_warning": false,
|
||||
"show_detailed_account_cards": true,
|
||||
"show_separate_partychat_button": true,
|
||||
"show_party_buttons_on_top": true,
|
||||
"show_goto_in_partychat_details": true,
|
||||
"use_party_invite_message": false,
|
||||
"show_party_chat_in_room_chat_menu": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"WrFpvkX2wvB6lQ5oD2mggKwqHWAKu/MDsbgZ6psncTc=": {
|
||||
"name": "WrFpvkX2wvB6lQ5oD2mggKwqHWAKu/MDsbgZ6psncTc=",
|
||||
"value": {
|
||||
"should_see_dormskin_button": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"DLrOzORmsC2awZcfRlQPulourg+mLgBIM6I+qAMqNpk=": {
|
||||
"name": "DLrOzORmsC2awZcfRlQPulourg+mLgBIM6I+qAMqNpk=",
|
||||
"value": {
|
||||
"save_cached_view_mem": true,
|
||||
"ios_low_max_mirror_mp": 1,
|
||||
"enabled": false,
|
||||
"ios_low_max_mp": 1,
|
||||
"memoryless_aux_surfaces_sharecam": true,
|
||||
"memoryless_aux_surfaces_cachedviews": true,
|
||||
"memoryless_aux_surfaces_pip": false,
|
||||
"memoryless_aux_surfaces_sniper_scope": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"oAyEfXY0oLzWjf8FWVaMe1HXqPCfm7BFZ6He5qgdyyk=": {
|
||||
"name": "oAyEfXY0oLzWjf8FWVaMe1HXqPCfm7BFZ6He5qgdyyk=",
|
||||
"value": {
|
||||
"optimize_expresso_ui": false,
|
||||
"optimize_raycasts": false,
|
||||
"optimize_nearest_canvas": false,
|
||||
"cache_uri_route_strings": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"OrBOJNLxfuXIM++mTxldtSHRWwMtRz5b3302A1vg5sA=": {
|
||||
"name": "OrBOJNLxfuXIM++mTxldtSHRWwMtRz5b3302A1vg5sA=",
|
||||
"value": {
|
||||
"Retention.Coordinate.BeaconMVPEnabled": true,
|
||||
"Retention.Coordinate.HintTutorialEnabled": false,
|
||||
"Retention.Coordinate.HeadsUpTutorialEnabled": true
|
||||
},
|
||||
"rule_id": "7xPuIlW2MzkBVJMdPlmFeW:override",
|
||||
"group": "7xPuIlW2MzkBVJMdPlmFeW:override",
|
||||
"group_name": "Heads up",
|
||||
"allocated_experiment_name": "OrBOJNLxfuXIM++mTxldtSHRWwMtRz5b3302A1vg5sA=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"Retention.Coordinate.BeaconMVPEnabled",
|
||||
"Retention.Coordinate.HintTutorialEnabled",
|
||||
"Retention.Coordinate.HeadsUpTutorialEnabled"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"csl2YojUOqMiH7gJ3YN6CjsX66jeFIwa9cH9h3ruGto=": {
|
||||
"name": "csl2YojUOqMiH7gJ3YN6CjsX66jeFIwa9cH9h3ruGto=",
|
||||
"value": {
|
||||
"UGC.Crowd.FitScoreMaximum": "0"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"6eAhOMuxQWhRLbhGamr/pLWKLy9gf1p5VSF5sgrvCf4=": {
|
||||
"name": "6eAhOMuxQWhRLbhGamr/pLWKLy9gf1p5VSF5sgrvCf4=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": false,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"allocated_experiment_name": "6eAhOMuxQWhRLbhGamr/pLWKLy9gf1p5VSF5sgrvCf4=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"PurchaseReminderPrefabVariantOverride",
|
||||
"UseBackgroundImageForPurchaseReminderOverride",
|
||||
"Override_Content_Json"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"84djck0QFqP6C1MV2A4gKk1mCS2at3GBSXjWq9iBRTU=": {
|
||||
"name": "84djck0QFqP6C1MV2A4gKk1mCS2at3GBSXjWq9iBRTU=",
|
||||
"value": {
|
||||
"use_online_ranking": true,
|
||||
"online_ranking_configuration_name": "2025q2_foryou_visited_in_session",
|
||||
"number_of_entities_to_shuffle": 18,
|
||||
"use_contextual_features": true,
|
||||
"algo_name": "HotRooms_ETSxCTRxRetentionBoost_CrashDemotionHigh",
|
||||
"algo_version": "1"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"gBMCCY/bTmDAZdtzYXZ8qpe9qLyBwohVVexprxFGN/Q=": {
|
||||
"name": "gBMCCY/bTmDAZdtzYXZ8qpe9qLyBwohVVexprxFGN/Q=",
|
||||
"value": {
|
||||
"algo_name": "text-embedding-004",
|
||||
"algo_version": "0",
|
||||
"number_of_entities_to_return": 100,
|
||||
"minimum_similarity_score": 0,
|
||||
"minimum_offline_ranking_score": 0,
|
||||
"embedding_dimension": 768,
|
||||
"number_of_entities_to_shuffle": 0
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"9ImoUIO/UFYFhdnGBchGpn1shDAb/Cd7Kby+09ma54Q=": {
|
||||
"name": "9ImoUIO/UFYFhdnGBchGpn1shDAb/Cd7Kby+09ma54Q=",
|
||||
"value": {
|
||||
"is_new_rrplus_page": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"EwdK/gXtW1dQzjn6nhBfs8zx1Mdw1RuKQ82KwVk9wNw=": {
|
||||
"name": "EwdK/gXtW1dQzjn6nhBfs8zx1Mdw1RuKQ82KwVk9wNw=",
|
||||
"value": {
|
||||
"third_person_feature_set": "FULL",
|
||||
"persistence": true,
|
||||
"Tip:Visibility": 0,
|
||||
"group_dance_with_gestures": true,
|
||||
"reduce_dance_options": false,
|
||||
"default_to_third_person": false,
|
||||
"toggle_in_orientation": true,
|
||||
"pinch_to_zoom": true,
|
||||
"tutorial_in_orientation": true,
|
||||
"new_melee": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"1eNWVgaLTvrQAiNFs93TTVVSBGCPCHH31JtC7pxlriw=": {
|
||||
"name": "1eNWVgaLTvrQAiNFs93TTVVSBGCPCHH31JtC7pxlriw=",
|
||||
"value": {
|
||||
"algo_name": "DefaultRankingAlgorithmName",
|
||||
"algo_version": "0"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"aIozXq/xzZoLKuwcvxwVq+NH+OIHV6uTgVWU/Y1Tof8=": {
|
||||
"name": "aIozXq/xzZoLKuwcvxwVq+NH+OIHV6uTgVWU/Y1Tof8=",
|
||||
"value": {
|
||||
"CuratedList_WWPanel.QuickAccess": "Discovery.PageSource.WWPanel.QuickAccess"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ig9p66CCZ+0tZvvrPN1jezlDuWJnT1bsBGNgJ0WkacA=": {
|
||||
"name": "ig9p66CCZ+0tZvvrPN1jezlDuWJnT1bsBGNgJ0WkacA=",
|
||||
"value": {
|
||||
"algo_name": "MiniLM_L6_v2",
|
||||
"algo_version": "1"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"VsZbXTlGa7dMWPTreDcLjT7zHP/CrmlJOmXVu5oS94w=": {
|
||||
"name": "VsZbXTlGa7dMWPTreDcLjT7zHP/CrmlJOmXVu5oS94w=",
|
||||
"value": {
|
||||
"is_daily_challenge_variations_enabled": false,
|
||||
"daily_objectives_override": {},
|
||||
"is_daily_challenge_discoverability_enabled": false,
|
||||
"is_new_ui": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"p9AAlhFHQh/xKBNOxyW/ypRSX4NDmeJvAyj5yxFBSbA=": {
|
||||
"name": "p9AAlhFHQh/xKBNOxyW/ypRSX4NDmeJvAyj5yxFBSbA=",
|
||||
"value": {
|
||||
"scalability_enabled": true,
|
||||
"scalability_preset": -1,
|
||||
"scalability_low_power": false,
|
||||
"scalability_scalemode": 4,
|
||||
"scalability_avatarenable": true,
|
||||
"scalability_textureenable": false,
|
||||
"scalability_shapesenable": true,
|
||||
"scalability_target_num_batches": 0,
|
||||
"scalability_max_triangles": 2000,
|
||||
"scalability_shapes_mem_delta": 400,
|
||||
"scalability_r1_allowed": -1,
|
||||
"scalability_r2_allowed": -1
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"hdBM1uoWYwex7QmkWbd+KKt9Fipq0uROhDVHNGidWxY=": {
|
||||
"name": "hdBM1uoWYwex7QmkWbd+KKt9Fipq0uROhDVHNGidWxY=",
|
||||
"value": {
|
||||
"boot_sequence_implementation_version": 31
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": true,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3568105362",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3568105362",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"+fB7z9fDNPrbw5iCU5VwIEBP14XpVd2bYuyAh0zQP8Y=": {
|
||||
"name": "+fB7z9fDNPrbw5iCU5VwIEBP14XpVd2bYuyAh0zQP8Y=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": false,
|
||||
"Override_Content_Json": "{ \"ImageName\": \"9n9d4vfl1wss3noodtexibixd.png\" }"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"uKPpMK3YsdGG4GtnJjoxQOHxFZVdJwKcZYm0GdmMEiY=": {
|
||||
"name": "uKPpMK3YsdGG4GtnJjoxQOHxFZVdJwKcZYm0GdmMEiY=",
|
||||
"value": {
|
||||
"card_variant_name": "small",
|
||||
"limited_badge_enabled": true,
|
||||
"limited_badge_details": true,
|
||||
"limited_time_display_days": 0,
|
||||
"limited_badge_threshold_days": 7
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"89EUXkk6Sl2jozLw5XxamwMCEHtD1nEg1RhDoaEMaEE=": {
|
||||
"name": "89EUXkk6Sl2jozLw5XxamwMCEHtD1nEg1RhDoaEMaEE=",
|
||||
"value": {
|
||||
"algo_name": "MiniLM_L6_v2",
|
||||
"algo_version": "1"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"o8rLdVBcwCtStHWUvLZoxUiAueW/YulhH8bjtoG2oAc=": {
|
||||
"name": "o8rLdVBcwCtStHWUvLZoxUiAueW/YulhH8bjtoG2oAc=",
|
||||
"value": {
|
||||
"loc_enabled": true,
|
||||
"GroupName": "Autoloc",
|
||||
"autoloc_enabled": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"QlLwPdjKkulpDG8VJdPC2VsTfbq0Ei0Jhl4PUwAH+oA=": {
|
||||
"name": "QlLwPdjKkulpDG8VJdPC2VsTfbq0Ei0Jhl4PUwAH+oA=",
|
||||
"value": {
|
||||
"remove_horizontal_scroll": true,
|
||||
"remove_chips": false,
|
||||
"remove_metadata_from_cards": false,
|
||||
"smallest_size": false,
|
||||
"mid_size": false,
|
||||
"largest_size": false,
|
||||
"PlayMenuCarouselOrdering_1": "hotrooms=24;new=23;recroomoriginals=22;continueplaying=21;rising=20;myfavorites=19;friendsplaying=18;topearning=17;mysubscriptions=16;inkincpremiumrooms=15;inkincroomhighlights=14;roleplay=13;casualplay=12;horror=11;battle=10;explore=9;quest=8;hangout=7;inkshowcase=6;featured=5;recommended=4;staffcurated=3;myclubhouses=2;myevents=1",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "",
|
||||
"GroupName": "Control",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincpremiumrooms=634448494387079051;inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.CardCountLimit": "0",
|
||||
"Lifecycle.PlayMenu.RoomCollectionsCarousel.Enabled": "false",
|
||||
"home_menu_disable_horizontal_scroll": "all",
|
||||
"view_more_button_text": "Similar Content",
|
||||
"open_content_menu": true,
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.RoomCardInfo": "[ { \"CCUCountRange\": { \"Min\": 0, \"Max\": -1 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] }, { \"CCUCountRange\": { \"Min\": -2, \"Max\": -2 }, \"RoomCardInfoElementIDs\": [ \"cheersIcon\", \"cheersText\" ] } ]",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.UIEnabled": "false",
|
||||
"Lifecycle.RoomDiscovery.CCUVariants.LoggingEnabled": "true",
|
||||
"Lifecycle.RoomDiscovery.Cheers.LoggingEnabled": "true",
|
||||
"ccu_load_synchronously": "false",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.NumRows": "",
|
||||
"Lifecycle.RoomDiscovery.MultiRowCarousels.BackgroundColors": "",
|
||||
"abbreviate_cheer_counts": "false",
|
||||
"multi_row_home": "Hot Rooms=2",
|
||||
"multi_row_play": "Hot Rooms=2",
|
||||
"multi_row_enabled": true,
|
||||
"multi_row_background_color": "#F3DDCE",
|
||||
"remove_trending_tags_chips": true,
|
||||
"rooms_grid_view_card_limit": 0
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"MugZRjrqb4ETe3s2KdIkc507cv1QLyLLrpqJrGN0J+8=": {
|
||||
"name": "MugZRjrqb4ETe3s2KdIkc507cv1QLyLLrpqJrGN0J+8=",
|
||||
"value": {
|
||||
"load_backpack_tools_on_demand": true,
|
||||
"load_room_chat_preview_on_demand": false,
|
||||
"load_room_chat_expanded_on_demand": true,
|
||||
"split_room_chat_by_vr_and_non_vr": true,
|
||||
"only_load_in_vr": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"mm19JXbrfz/Bz1q7c8K5NSc0U8VBQ0K7mh4GQl6I8sc=": {
|
||||
"name": "mm19JXbrfz/Bz1q7c8K5NSc0U8VBQ0K7mh4GQl6I8sc=",
|
||||
"value": {
|
||||
"allow_play_together": false,
|
||||
"play_together_max_rooms": 5,
|
||||
"play_together_playlist_name": "",
|
||||
"play_together_use_recents": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"KKv5XO5euk0rCniD9LTaQJ/2qTaE/uTaFkLenCOdD4A=": {
|
||||
"name": "KKv5XO5euk0rCniD9LTaQJ/2qTaE/uTaFkLenCOdD4A=",
|
||||
"value": {
|
||||
"auto_connect_enabled": false,
|
||||
"recommendations_enabled": false,
|
||||
"drag_and_release_enabled": true,
|
||||
"smart_circuit_enabled": true,
|
||||
"spawn_time_threshold_seconds": 0.5,
|
||||
"spawn_indicator_threshold_seconds": 0.3,
|
||||
"spawn_distance_threshold": 0.005,
|
||||
"dismiss_distance_threshold": 0.5,
|
||||
"max_allowed_dialog_distance": 1,
|
||||
"max_dialog_alignment_distance": 2.5,
|
||||
"max_recommendations_per_port": 50,
|
||||
"ideal_recommendations_no": 25,
|
||||
"backfill_recommendation_results": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"6Xr6MySBxHr0xDk+0U9U4/gInmfs/UyuLXJh+/+Pxik=": {
|
||||
"name": "6Xr6MySBxHr0xDk+0U9U4/gInmfs/UyuLXJh+/+Pxik=",
|
||||
"value": {
|
||||
"voice_call_survey_canned_options": [
|
||||
"Couldn't Hear Others",
|
||||
"Others Couldn't Hear Me",
|
||||
"Voice Glitchy/Distorted",
|
||||
"Voices Cut Out/Break Up",
|
||||
"Voices Delayed/Lagged",
|
||||
"Voices Echo",
|
||||
"Other"
|
||||
],
|
||||
"room_gain_threshold": 0.55,
|
||||
"call_gain_threshold": 0.5,
|
||||
"mutually_exclusive_voice_source": true,
|
||||
"show_survey_when_leaving_call": true,
|
||||
"dynamic_switching_if_private": false,
|
||||
"current_group_name": "Show Survey",
|
||||
"survey_send_game_logs_bug_report": true,
|
||||
"voice_call_volume_percentage_default": 1,
|
||||
"show_voice_call_volume_slider": true,
|
||||
"should_deprecate_ugc_voice_channel": true,
|
||||
"Retention.IndividualVoiceVolume.Enabled": "",
|
||||
"is_individual_voice_volume_enabled": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RwNQPr5nD46VZMEo70b/kPDzBIsoN0ezSeoGdGqUt1c=": {
|
||||
"name": "RwNQPr5nD46VZMEo70b/kPDzBIsoN0ezSeoGdGqUt1c=",
|
||||
"value": {
|
||||
"ShareCam.VideoEnabled": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"LgBBafNbxB8MgnRL76RgLlW2GMij9JKUjNj5bG0UPaA=": {
|
||||
"name": "LgBBafNbxB8MgnRL76RgLlW2GMij9JKUjNj5bG0UPaA=",
|
||||
"value": {
|
||||
"use_curated_controls": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "RoomID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"r3N9tJopvhzi/b+cLGaq9xUXjqtLgo5St8YSg2vitDg=": {
|
||||
"name": "r3N9tJopvhzi/b+cLGaq9xUXjqtLgo5St8YSg2vitDg=",
|
||||
"value": {
|
||||
"touch_radial_menu": true,
|
||||
"radial_roominventory_page": false,
|
||||
"radial_icon": "BACKPACK",
|
||||
"radial_consumables_page": false,
|
||||
"lower_roominventory_button": "MENU_GRID",
|
||||
"radial_watch_link_icon": "MENU_GRID",
|
||||
"radial_watch_link_subscreen": "Default"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"jkKmDuuECNMqp5K9E4/EJ9Z9KtT0DofztRWqT6IB39M=": {
|
||||
"name": "jkKmDuuECNMqp5K9E4/EJ9Z9KtT0DofztRWqT6IB39M=",
|
||||
"value": {
|
||||
"preallocate_memory_bytes": 0,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 0,
|
||||
"fix_player_reference_leaks": true,
|
||||
"gc_collect_10x_on_low_memory": false,
|
||||
"gc_collect_10x_at_empty_scene": false,
|
||||
"gc_collect_on_low_memory": 0,
|
||||
"Debug.Watchdog.CorruptRoomDetectionProbability": "1",
|
||||
"Rooms.RoomCrashWarnings.WarningType": "None"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"AisW+Kj1C2OrSEqNs/lHWW1GuazwXe4tff19pFstaUo=": {
|
||||
"name": "AisW+Kj1C2OrSEqNs/lHWW1GuazwXe4tff19pFstaUo=",
|
||||
"value": {
|
||||
"use_custom_backpack_icon": false,
|
||||
"show_games_tab": true,
|
||||
"Tip:Visibility": 0,
|
||||
"DataCollection.LogFriendIdsWithSocialHeartbeat": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"FpdKUcfZI6CsAJ5hIJUEkJDAJPApEdJu96jNeQYqJjc=": {
|
||||
"name": "FpdKUcfZI6CsAJ5hIJUEkJDAJPApEdJu96jNeQYqJjc=",
|
||||
"value": {
|
||||
"disable_roomdata_cache": false,
|
||||
"disable_invention_cache": false,
|
||||
"disable_datablob_cache": false,
|
||||
"besthttp_bufferpool_maxbuffersize": 999999999,
|
||||
"besthttp_bufferpool_maxpoolsize": 10485760,
|
||||
"besthttp_bufferpool_allowexactsize": true,
|
||||
"besthttp_bufferpool_usearraypool": false,
|
||||
"use_besthttp_request_queue": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"yYlys3qqNVnx1ct3lYenNFTSa4QFGjKdURuJW/GKVuA=": {
|
||||
"name": "yYlys3qqNVnx1ct3lYenNFTSa4QFGjKdURuJW/GKVuA=",
|
||||
"value": {
|
||||
"use_infinite_list": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"67L82m4NFrjfullqw9r2q4+t8GCv2w5XpYozZfE5qcY=": {
|
||||
"name": "67L82m4NFrjfullqw9r2q4+t8GCv2w5XpYozZfE5qcY=",
|
||||
"value": {
|
||||
"DefaultUnlockedValueThreshold": 3,
|
||||
"DailyTokenCapOverride": 6217911,
|
||||
"MaximumPostActivityRewardCountOverride": 10,
|
||||
"GiftContextOverride": "GameRewards",
|
||||
"RewardProbabilityOverride": 1,
|
||||
"TokenGiftDrop:Common": 3397,
|
||||
"TokenGiftDrop:Common:Subscribers": 4013,
|
||||
"TokenGiftDrop:Uncommon": 3396,
|
||||
"TokenGiftDrop:Uncommon:Subscribers": 4014,
|
||||
"TokenGiftDrop:Rare": 3395,
|
||||
"TokenGiftDrop:Rare:Subscribers": 4015,
|
||||
"TokenGiftDrop:Epic": 3394,
|
||||
"TokenGiftDrop:Epic:Subscribers": 4016,
|
||||
"IsSearchWithTagsEnabled": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"S1Fds84nmsRQ8kRUQmSRPA1gB7xtOv+ymgxhQM93dOU=": {
|
||||
"name": "S1Fds84nmsRQ8kRUQmSRPA1gB7xtOv+ymgxhQM93dOU=",
|
||||
"value": {
|
||||
"show_store_items_in_room_details": false,
|
||||
"carousel_title": "Recommended for this Room"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"LIETKb0irpA1DX53lnMLw5tbcPYlj95aOarkT0LELwI=": {
|
||||
"name": "LIETKb0irpA1DX53lnMLw5tbcPYlj95aOarkT0LELwI=",
|
||||
"value": {
|
||||
"use_optimized_community_board_prefab": true,
|
||||
"conditionally_spawn_playmenu_banner": true,
|
||||
"conditionally_spawn_progression_events_intro_banner": true,
|
||||
"conditionally_spawn_dev_tools": true,
|
||||
"conditionally_spawn_scroll_bars": true,
|
||||
"conditionally_spawn_if_not_max_level": true,
|
||||
"conditionally_spawn_if_contains_room_warnings": true,
|
||||
"conditionally_spawn_if_cannot_join_without_locomotion_switch": true,
|
||||
"DataCollection.SkipPerfStatsFor": ""
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"CDBRhrf6GRhelgr3mj079XwD3Y1N4or1qACsFaJJ05Y=": {
|
||||
"name": "CDBRhrf6GRhelgr3mj079XwD3Y1N4or1qACsFaJJ05Y=",
|
||||
"value": {
|
||||
"full_body_avatars_enabled": true,
|
||||
"EnableDownloadableAvatarItems": false,
|
||||
"modern_bean": false,
|
||||
"UseFourBoneSkinnedMeshRenderers": true,
|
||||
"UseVolumeBasedHipSlotsPickup": true,
|
||||
"UseModernFloatingBeanAvatar": true,
|
||||
"avatar_body_type_default": "FullBodyType",
|
||||
"ShowBottomsAndShoesTabs": true,
|
||||
"default_nux_customize_tab_name": "Tops",
|
||||
"ModernBeanBodyAvatars.UseClassicBeanOverrideEnabled": "true",
|
||||
"setup_avatar_skeleton_dynamically": true,
|
||||
"use_potato_avatar": true,
|
||||
"UseClassicBeanOverrideEnabled": false,
|
||||
"default_tab_name": "Tops",
|
||||
"disable_classic_bean_keep_switch": false,
|
||||
"force_modern_bean_one_time": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "101598084",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "101598084",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"9ILU8v6XbPSE1PRobOrfi3IYd/2kanF9h13tXjnOF1c=": {
|
||||
"name": "9ILU8v6XbPSE1PRobOrfi3IYd/2kanF9h13tXjnOF1c=",
|
||||
"value": {
|
||||
"variant": "LARGE_LIVE_TQ"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"GaNBacwnGqzQI0RZFRyoo91/5cJgpDkH5Z7/8vy66hY=": {
|
||||
"name": "GaNBacwnGqzQI0RZFRyoo91/5cJgpDkH5Z7/8vy66hY=",
|
||||
"value": {
|
||||
"CompressRecNetImages": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"/i98QEcoxEwp53cVPHvuNehKS9op9B0eg9EEygnVI+I=": {
|
||||
"name": "/i98QEcoxEwp53cVPHvuNehKS9op9B0eg9EEygnVI+I=",
|
||||
"value": {
|
||||
"removePortraitMode": false,
|
||||
"recnet_deserialize_json_on_background_thread_length_threshold": 1000,
|
||||
"recnet_use_besthttp_request_queue": false,
|
||||
"parallelize_photon_connection": true,
|
||||
"start_critical_operations_early": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": true,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1195813645",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "795959371",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7zLAnzZ4AiAvCAgX6xB3ZL:5.00:1"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1195813645",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "795959371",
|
||||
"gateValue": "false",
|
||||
"ruleID": "7zLAnzZ4AiAvCAgX6xB3ZL:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"/GFGa1NTEQOZ1eJCzXY2QYCDVighp9eGjlZvMCAKaj0=": {
|
||||
"name": "/GFGa1NTEQOZ1eJCzXY2QYCDVighp9eGjlZvMCAKaj0=",
|
||||
"value": {
|
||||
"IsUserAllowlistedForAvatarStudio": false,
|
||||
"AdditionalOutfitTypeEnumNamesPermitted": [],
|
||||
"CanCreateRoomieEquipment": false,
|
||||
"OutfitTypeEnumNamesPermitted": []
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Ya+fsXd0r4q/InD0fa669Urh4JlRofTMtAIr4DIlSFE=": {
|
||||
"name": "Ya+fsXd0r4q/InD0fa669Urh4JlRofTMtAIr4DIlSFE=",
|
||||
"value": {
|
||||
"isTokenStoreEnabled": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"sbSdcanBzxfOo6vhHXIPfD+OJr51hfmdAbRZILXcQhc=": {
|
||||
"name": "sbSdcanBzxfOo6vhHXIPfD+OJr51hfmdAbRZILXcQhc=",
|
||||
"value": {
|
||||
"algo_name": "DefaultRankingAlgorithmName",
|
||||
"algo_version": "0"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"N51FtntSrClGwYLZCSnwPj+/FaUwZ+1WUraHnzAL43E=": {
|
||||
"name": "N51FtntSrClGwYLZCSnwPj+/FaUwZ+1WUraHnzAL43E=",
|
||||
"value": {
|
||||
"algo_name": "Rooms_MiniLM_L6_v2",
|
||||
"algo_version": "1"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"xKg3USlinBRpIMkIC/FkmHqSEvehxAKl5bDJw3QS/Y4=": {
|
||||
"name": "xKg3USlinBRpIMkIC/FkmHqSEvehxAKl5bDJw3QS/Y4=",
|
||||
"value": {
|
||||
"enable_dir_nav": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"VpItEm97pOkxEa44Jxp+n3BwuN+W+rAyu9v7bhp26Hs=": {
|
||||
"name": "VpItEm97pOkxEa44Jxp+n3BwuN+W+rAyu9v7bhp26Hs=",
|
||||
"value": {
|
||||
"StartTime": "2030-01-01T00:00:00Z",
|
||||
"EndTime": "2030-01-02T00:00:00Z",
|
||||
"TLA.Config": "{}",
|
||||
"TLA.Enabled": "false",
|
||||
"IsEnabled": false,
|
||||
"LiveOps.TLA.UpcomingOrActiveId": "",
|
||||
"LiveOps.TLA.Displacement.Enabled": false,
|
||||
"LiveOps.TLA.Displacement.DPM": "true",
|
||||
"TeamRegistrationStartTime": "2030-01-01T00:00:00Z",
|
||||
"TeamRegistrationEndTime": "2030-01-02T00:00:00Z"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ErwSKV/tsZVy+8lVeUmHteTnNUUoowNLSiSa7TWBFCU=": {
|
||||
"name": "ErwSKV/tsZVy+8lVeUmHteTnNUUoowNLSiSa7TWBFCU=",
|
||||
"value": {
|
||||
"show_playaction_button": false,
|
||||
"default_instance_type": "",
|
||||
"show_morelikethis_button": false,
|
||||
"supported_carousels": [],
|
||||
"new_rooms_threshold_days": 30,
|
||||
"hide_cheers_for_new": true,
|
||||
"hide_cheers_count": true,
|
||||
"show_cheers_data_token": false,
|
||||
"show_new_data_token": true,
|
||||
"loop_shimmer_animation": false,
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"show_publish_state": true,
|
||||
"Creator.Room.EnableRoomTokens": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"2vfL4ldVtAwWS8hcVm/HIRnbiM3l18GxN2Flx8Yvw1o=": {
|
||||
"name": "2vfL4ldVtAwWS8hcVm/HIRnbiM3l18GxN2Flx8Yvw1o=",
|
||||
"value": {
|
||||
"sort_criteria": "AppearsOnline"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"piW4ewt8QVcZkL9PzgFCg1U3NuXAZCx9HC1LzzHJ5jI=": {
|
||||
"name": "piW4ewt8QVcZkL9PzgFCg1U3NuXAZCx9HC1LzzHJ5jI=",
|
||||
"value": {
|
||||
"show_room_capacity_party_size_warning": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"72HcJUk/Or3fGReaKo3DUBAFugnF1t4LB7WA6hd2gT4=": {
|
||||
"name": "72HcJUk/Or3fGReaKo3DUBAFugnF1t4LB7WA6hd2gT4=",
|
||||
"value": {
|
||||
"boot_strategy": 3
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": true,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"lNpOC2+wjXveFGeYWvbtYb/GKg7NZMtjoUKyEXV6ql8=": {
|
||||
"name": "lNpOC2+wjXveFGeYWvbtYb/GKg7NZMtjoUKyEXV6ql8=",
|
||||
"value": {
|
||||
"CuratedList_AppNav.WidgetPanel.Profile": "Discovery.PageSource.WWPanel.Profile",
|
||||
"CuratedList_WWPanel.Profile": "Discovery.PageSource.WWPanel.Profile"
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"allocated_experiment_name": "oEUIAee3SnooR91A81fzA9cmIlHlFXoi/dsApjVoMCs=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"CuratedList_WWPanel.Profile"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"BnLOhfyW7Wssls05mX+xaDWzGOe0A03m9XEygLL/5RQ=": {
|
||||
"name": "BnLOhfyW7Wssls05mX+xaDWzGOe0A03m9XEygLL/5RQ=",
|
||||
"value": {
|
||||
"algo_name": "SVD10",
|
||||
"algo_version": "1"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"07yfXe/9FtClkVMTA8tCLJPMjCvPvWhaHJZG6QYJDiw=": {
|
||||
"name": "07yfXe/9FtClkVMTA8tCLJPMjCvPvWhaHJZG6QYJDiw=",
|
||||
"value": {
|
||||
"CustomAvatarItemSets.DefaultItemSetItemsSelected": "false",
|
||||
"CustomAvatarItemSets.ItemTypeOrdering": [
|
||||
0,
|
||||
101,
|
||||
105,
|
||||
100,
|
||||
102,
|
||||
103,
|
||||
200,
|
||||
2,
|
||||
3,
|
||||
10,
|
||||
20,
|
||||
300,
|
||||
301
|
||||
],
|
||||
"CustomAvatarItemSets.NumItemsToShow": 5,
|
||||
"CustomAvatarItemSets.PanelTitleDynamic": "Popular Items in This Set"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"JIPlhp+YzBmvJIERCCxOuv/nAPhQ1DMeUxTOVQJCaT8=": {
|
||||
"name": "JIPlhp+YzBmvJIERCCxOuv/nAPhQ1DMeUxTOVQJCaT8=",
|
||||
"value": {
|
||||
"allow_juniors_quick_chat": false,
|
||||
"include_online_friends": true,
|
||||
"friends_default_tab": true,
|
||||
"default_friends_if_people_empty": false,
|
||||
"show_detailed_player_search_cards": true,
|
||||
"show_empty_search_call_to_action": true,
|
||||
"hide_local_people_search": false,
|
||||
"show_search_players_text_for_remote_search": true,
|
||||
"show_progression_on_watch_home": true,
|
||||
"show_progression_profile": true,
|
||||
"show_progression_notifications": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Ly4PNYjrjruOJHMBsr51/s3NBzaqxhKyOp6+t/69l3E=": {
|
||||
"name": "Ly4PNYjrjruOJHMBsr51/s3NBzaqxhKyOp6+t/69l3E=",
|
||||
"value": {
|
||||
"disable_newplayerchecklist": true,
|
||||
"show_progression_on_watch_home": true,
|
||||
"show_progression_profile": true,
|
||||
"show_progression_notifications": true,
|
||||
"show_progression_loading": true,
|
||||
"Tip:Visibility": 1
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Xm2CjRjmipYjlTlloaVGTtTBlONBlLsMO4Oplxhlq6U=": {
|
||||
"name": "Xm2CjRjmipYjlTlloaVGTtTBlONBlLsMO4Oplxhlq6U=",
|
||||
"value": {
|
||||
"algo_name": "InventionsHotScoreTrending",
|
||||
"algo_version": "5",
|
||||
"show_curated_list": true
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"allocated_experiment_name": "YTGf47KSC7UnzcrMO9DCUn0EI9MA8El8KCou8+L46EY=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"show_curated_list"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"21rMPXw4rpQu/m3XdUNDjg3VlQXFbc8OGKhTdWfcp7Y=": {
|
||||
"name": "21rMPXw4rpQu/m3XdUNDjg3VlQXFbc8OGKhTdWfcp7Y=",
|
||||
"value": {
|
||||
"useOptimizedSchedulerUpdateQueues": false,
|
||||
"optimizeRbexSleep": false,
|
||||
"optimizeSyncParent": true,
|
||||
"slowdown_duration_ms": 0,
|
||||
"hitch_interval_ms": 0,
|
||||
"hitch_duration_ms": 0,
|
||||
"targetFramerate": 0,
|
||||
"maintain_fps": 0,
|
||||
"smallUpdateOptimizations2": true,
|
||||
"cullOldNetworkTransformSyncData": true,
|
||||
"opt_android_reliable_ticks": true,
|
||||
"optimizeQuestUmbraCulling": true,
|
||||
"optimizeDecals": true,
|
||||
"srp_batch_support_decals": true,
|
||||
"srp_batch_support_makerpen": true,
|
||||
"srp_batch_support_avatars": true,
|
||||
"srp_batch_healthbars": true,
|
||||
"optimizeFaceCameraUIAnchorSync": true,
|
||||
"useCachedViewPostprocessingOptimizations": true,
|
||||
"enableRRStudioCanvasCulling": true,
|
||||
"optimizeVoiceLinesController2D": true,
|
||||
"optimizeDistanceFadeManager": true,
|
||||
"enableCanvasDistanceCulling": true,
|
||||
"optimizeRbexSleepKinematicCheck": true,
|
||||
"optimizeSFXAndParticleReleaseMonitoring": true,
|
||||
"max_fixedUpdates_onMobile": 0,
|
||||
"optimizeTerrain": true,
|
||||
"optimizePhysicalTextUpdate": true,
|
||||
"optimizeConnectedGrabbableDecorationColliders": false,
|
||||
"optimize_fastgraphicraycaster_visibility": true,
|
||||
"disableUIAnchorFaceCameraForMirrors": true,
|
||||
"cullRemotePlayerMovementFeedbackParticles": true,
|
||||
"optimizeScreenInputHandlerUpdateStateForVR": true,
|
||||
"enableMuzzleFlashDistanceCulling": true,
|
||||
"fixAudioDistanceChecks": false,
|
||||
"optimize_avatar_network_interpolation": true,
|
||||
"useMirrorOcclusionCulling": true,
|
||||
"useNametagNonUGUI": true,
|
||||
"forceIOSHitching": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ITijGlO24Im08gJQqEkQwhAYnLcQLr+N+TN6VuFuLso=": {
|
||||
"name": "ITijGlO24Im08gJQqEkQwhAYnLcQLr+N+TN6VuFuLso=",
|
||||
"value": {
|
||||
"place_r2_support_enabled": true,
|
||||
"place_improved_rotation_enabled": true,
|
||||
"place_touch_depth_enabled": true,
|
||||
"place_touch_tutorial_enabled": true,
|
||||
"place_max_tool_distance": 3,
|
||||
"place_improved_large_objects_enabled": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jjzsLxr3pPxgpY3DYkLqeMX98J+DUrkWKdGpRRqKD/E=": {
|
||||
"name": "jjzsLxr3pPxgpY3DYkLqeMX98J+DUrkWKdGpRRqKD/E=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "double",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": true,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Q1u67Qa9SqRDGRtzdOk0aXJ/n9we0f8r2qJzTI6af5Q=": {
|
||||
"name": "Q1u67Qa9SqRDGRtzdOk0aXJ/n9we0f8r2qJzTI6af5Q=",
|
||||
"value": {},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"BoluQzKjk8Hqw97cpCEZy0vH9o01x1/P/c6VG/Jzw+I=": {
|
||||
"name": "BoluQzKjk8Hqw97cpCEZy0vH9o01x1/P/c6VG/Jzw+I=",
|
||||
"value": {
|
||||
"EnabledFlag": 0,
|
||||
"MatchmakingUI.ShowIfEveryoneLeaves": "false"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"X10jgq2r6wWEgJy5relxRp0fRar7BvrbiCXP9+eLwME=": {
|
||||
"name": "X10jgq2r6wWEgJy5relxRp0fRar7BvrbiCXP9+eLwME=",
|
||||
"value": {
|
||||
"show_x_to_close": true,
|
||||
"show_ignore_for_now": false,
|
||||
"improve_room_chat_visuals": true,
|
||||
"add_room_chat_emojis": true,
|
||||
"Lifecycle.PSF.RestrictUnicodeSupportInRoomChat": "false",
|
||||
"max_room_chat_preview_lines": 5,
|
||||
"max_room_chat_expanded_lines": 21,
|
||||
"show_select_unselect_all_button": false,
|
||||
"preselect_all_accounts": false,
|
||||
"show_modified_dont_invite_button": true,
|
||||
"show_modified_invite_button": false,
|
||||
"emphasize_reccenter_button": false,
|
||||
"should_autofocus": true,
|
||||
"should_autofocus_search": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"DW7iHLSnyFPq9Q38EIfIpxRkuGbRANqblOAXI0u1yDo=": {
|
||||
"name": "DW7iHLSnyFPq9Q38EIfIpxRkuGbRANqblOAXI0u1yDo=",
|
||||
"value": {
|
||||
"allow_jr_room_chat": true,
|
||||
"room_chat_toggle_position": "TopRight"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"du4aJKASsUxjUws/seSmCh1uQe8swG2Xr0RrBg6YvFE=": {
|
||||
"name": "du4aJKASsUxjUws/seSmCh1uQe8swG2Xr0RrBg6YvFE=",
|
||||
"value": {
|
||||
"react_enable_speedy_radial": true,
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [
|
||||
"TOGGLE_PERSPECTIVE:-1",
|
||||
"GLOBAL_EMOTES:2",
|
||||
"GLOBAL_MIC:0",
|
||||
"GLOBAL_GESTURES:1"
|
||||
],
|
||||
"fade_react_joystick": true,
|
||||
"show_react_joystick": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"EZq2LJw/m0cMuNF/p3Nm5gIn0H3E6sTEro7Dg7alBP0=": {
|
||||
"name": "EZq2LJw/m0cMuNF/p3Nm5gIn0H3E6sTEro7Dg7alBP0=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": false,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"allocated_experiment_name": "EZq2LJw/m0cMuNF/p3Nm5gIn0H3E6sTEro7Dg7alBP0=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"PurchaseReminderPrefabVariantOverride",
|
||||
"UseBackgroundImageForPurchaseReminderOverride",
|
||||
"Override_Content_Json"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"58u/LpVluWTINaE2UWs50wAuOTXJzWkdsHMzWw9GhLs=": {
|
||||
"name": "58u/LpVluWTINaE2UWs50wAuOTXJzWkdsHMzWw9GhLs=",
|
||||
"value": {
|
||||
"ReduceAvataritemCalls": false,
|
||||
"ReduceStorefrontCalls": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": true,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"OKT92xAnPqNF9PzMBBi4hZy/bUrt+rIzPHXJaTAnZN8=": {
|
||||
"name": "OKT92xAnPqNF9PzMBBi4hZy/bUrt+rIzPHXJaTAnZN8=",
|
||||
"value": {
|
||||
"isEnabled": false,
|
||||
"cooldown_base": 86400,
|
||||
"cooldown_scale": 0,
|
||||
"cooldown_min": 86400,
|
||||
"cooldown_max": 604800,
|
||||
"cooldown_max_views": -1,
|
||||
"is_enabled_chat": true,
|
||||
"is_enabled_event": false,
|
||||
"is_enabled_gift": true
|
||||
},
|
||||
"rule_id": "2uVnVcb3eXYFjGGcyf0QFb:override",
|
||||
"group": "2uVnVcb3eXYFjGGcyf0QFb:override",
|
||||
"group_name": "Test",
|
||||
"allocated_experiment_name": "xLn0QOi9e3jktAfDQtg+c+A6ECzCnEl72ReOPoi6D+I=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"cooldown_base",
|
||||
"cooldown_scale",
|
||||
"cooldown_min",
|
||||
"cooldown_max",
|
||||
"cooldown_max_views",
|
||||
"is_enabled_gift",
|
||||
"is_enabled_chat"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "930626607",
|
||||
"gateValue": "true",
|
||||
"ruleID": "4gQtmddP2Gauv81AqWUi7a"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "930626607",
|
||||
"gateValue": "true",
|
||||
"ruleID": "4gQtmddP2Gauv81AqWUi7a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"5593eXQ1KuL6AMfyLFf/8O9YsQ9yxXA8lYK5w/NILl4=": {
|
||||
"name": "5593eXQ1KuL6AMfyLFf/8O9YsQ9yxXA8lYK5w/NILl4=",
|
||||
"value": {
|
||||
"link_to_custom_endpoint": false,
|
||||
"endpoint_name": "itemsforyou",
|
||||
"page_title": "Recommended For You",
|
||||
"entity_type": "",
|
||||
"source": "AlgorithmicList"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"bZD06mzQWOrtShq4QrfkCOvVByI1ur4BX5QTDVQzcUw=": {
|
||||
"name": "bZD06mzQWOrtShq4QrfkCOvVByI1ur4BX5QTDVQzcUw=",
|
||||
"value": {
|
||||
"CuratedList_WWPanelList": "WWPanelList.Chaos"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"/0xCncAYxzQQc7p1dJqspJZgnIUejlpnS25aPz8gCuk=": {
|
||||
"name": "/0xCncAYxzQQc7p1dJqspJZgnIUejlpnS25aPz8gCuk=",
|
||||
"value": {
|
||||
"height_adjustment": -0.1
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vYBPoLQRVIHP/ruf0tX/FTATFseGs537O9oYvGYOS3U=": {
|
||||
"name": "vYBPoLQRVIHP/ruf0tX/FTATFseGs537O9oYvGYOS3U=",
|
||||
"value": {
|
||||
"show_item_card_buy_button": true,
|
||||
"allow_resized_storefronts": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"S3LM1iCAGmILaS6mEaqHQ4lWV9ekCd4828v7YPuuStg=": {
|
||||
"name": "S3LM1iCAGmILaS6mEaqHQ4lWV9ekCd4828v7YPuuStg=",
|
||||
"value": {
|
||||
"use_new_room_card": true,
|
||||
"use_new_club_event_card": true,
|
||||
"use_new_item_cards": true,
|
||||
"use_new_account_cards": true,
|
||||
"use_vnext_db_room_carousels": true
|
||||
},
|
||||
"rule_id": "6Utq2t47jKIzh3JZzKNSd4:override",
|
||||
"group": "6Utq2t47jKIzh3JZzKNSd4:override",
|
||||
"group_name": "Test",
|
||||
"allocated_experiment_name": "HGrJQ7tTnzVtOuiqlNRFb3zoVS5yehgoySHlLzbfwbk=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"use_vnext_db_room_carousels"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3668620673",
|
||||
"gateValue": "true",
|
||||
"ruleID": "5wzMtLjSXYmWPzy1fJq0BN"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3668620673",
|
||||
"gateValue": "true",
|
||||
"ruleID": "5wzMtLjSXYmWPzy1fJq0BN"
|
||||
}
|
||||
]
|
||||
},
|
||||
"72NOZV1epsB4qZYKmYU8KlaobkKgj+J/bNrq7aZcMj4=": {
|
||||
"name": "72NOZV1epsB4qZYKmYU8KlaobkKgj+J/bNrq7aZcMj4=",
|
||||
"value": {
|
||||
"Retention.Coordinate.BeaconMVPEnabled": false,
|
||||
"Retention.Coordinate.AllowVRPlayerBeaconControls": false,
|
||||
"Retention.Coordinate.UseWiderVRLaserWhenBeaconsEnabled": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "706125647",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "706125647",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"HpJEClHU+SVFCI4CnRiHK2DJAX/f6ljnVnqKJ00j91k=": {
|
||||
"name": "HpJEClHU+SVFCI4CnRiHK2DJAX/f6ljnVnqKJ00j91k=",
|
||||
"value": {
|
||||
"enabled": true,
|
||||
"button_label": "Make Your Own",
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"button_icon": "clone_room_button_b"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"MxgunVTvYesafrx8DswGG4DbdQfAxDrVlvWKBD4X8L8=": {
|
||||
"name": "MxgunVTvYesafrx8DswGG4DbdQfAxDrVlvWKBD4X8L8=",
|
||||
"value": {
|
||||
"is_in_test": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"u1E+vov1nCNMTQ0pzDBW499lN80Qosvom1TXZFU4Gh4=": {
|
||||
"name": "u1E+vov1nCNMTQ0pzDBW499lN80Qosvom1TXZFU4Gh4=",
|
||||
"value": {
|
||||
"Retention.Coordinate.PartyBeaconEnabled": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dVMEW6rcNOE/M7G7qa5XDyT09eODueaK1H4hUYeJPA4=": {
|
||||
"name": "dVMEW6rcNOE/M7G7qa5XDyT09eODueaK1H4hUYeJPA4=",
|
||||
"value": {
|
||||
"show_revamped_upsell_reward_picker": true,
|
||||
"upsell_decline_cooldown_seconds": 86400
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"o7wrxacF5BvpLBaxl3HYnXxCFjPR53nHU1Yb6p/qau8=": {
|
||||
"name": "o7wrxacF5BvpLBaxl3HYnXxCFjPR53nHU1Yb6p/qau8=",
|
||||
"value": {
|
||||
"param1": "",
|
||||
" param1": 0,
|
||||
"param1 ": []
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"cgb8nf/OK4kde3AATRAmlWJkiDnLw7EiqxWn1f70/mE=": {
|
||||
"name": "cgb8nf/OK4kde3AATRAmlWJkiDnLw7EiqxWn1f70/mE=",
|
||||
"value": {
|
||||
"Notification:IsEnabled": true,
|
||||
"Group Name": "",
|
||||
"GroupName": "Test",
|
||||
"Config:IsEnabled": true,
|
||||
"Notification:Number": 0,
|
||||
"Notification:Priority": 0,
|
||||
"Notification:Title": "🎁A Special GIFT for you from Rec Room!🎁",
|
||||
"Category:RateLimitWindowDurationSeconds": 0,
|
||||
"Category:RateLimitMaxNotifications": 0,
|
||||
"Category:IsRateLimitedByCategory": false,
|
||||
"RateLimitWindowDurationSeconds": "",
|
||||
"RateLimitMaxNotifications": 5,
|
||||
"ClubChatRateLimitWindowDurationSeconds": 900,
|
||||
"ClubChatRateLimitMaxNotifications": 5,
|
||||
"Notification:Body": "You're only 3 steps away from unlocking your daily reward 📆",
|
||||
"Notification:DisplayTexts:0:Title": "🎁Unopened Gifts",
|
||||
"Notification:DisplayTexts:1:Title": "🎁FREE gift 🤩",
|
||||
"Notification:DisplayTexts:2:Title": "🎁Come COLLECT🔥",
|
||||
"Notification:DisplayTexts:0:Body": "You have an unopened gift waiting for you in Rec Room!",
|
||||
"Notification:DisplayTexts:1:Body": "Your unopened gift is waiting for you! Claim NOW!✨",
|
||||
"Notification:DisplayTexts:2:Body": "🎁🧡🤩📦🌈✨ 🙌",
|
||||
"Notification:DisplayTexts:3:Title": "🚨Don’t miss out!🚨",
|
||||
"Notification:DisplayTexts:4:Title": "You’re gift awaits!🎁",
|
||||
"Notification:DisplayTexts:3:Body": "Your unopened gift awaits! 🎁🧡🤩📦🌈✨Come check out what Rec Roomers are playing now!",
|
||||
"Notification:DisplayTexts:4:Body": "🧡🤩📦🌈✨Don’t miss out on this fun gift!",
|
||||
"Category:ShouldSendToOnlinePlayers": false,
|
||||
"roomNotificationTreatmentType": "Control",
|
||||
"BrazeNotificationTreatmentType": "",
|
||||
"RengageNotificationTreatmentType": "Control"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2345755404",
|
||||
"gateValue": "false",
|
||||
"ruleID": "1RyXaibUwiHWUoNGlHTkMJ:5.00:1"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "2345755404",
|
||||
"gateValue": "false",
|
||||
"ruleID": "1RyXaibUwiHWUoNGlHTkMJ:5.00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"kb57DVRLuQgK5SIjQWL8EB+a/OqGG8v9WWDmG+YZPmI=": {
|
||||
"name": "kb57DVRLuQgK5SIjQWL8EB+a/OqGG8v9WWDmG+YZPmI=",
|
||||
"value": {
|
||||
"rrplus_paywall_type": "prevent_confirming_creation",
|
||||
"number_of_saved_outfits": 100,
|
||||
"show_mirror_button": true,
|
||||
"show_top_level_tab": true,
|
||||
"show_customizer_card": true,
|
||||
"show_back_facing_custom_shirt_preview": false,
|
||||
"show_new_customizer_buttons": true,
|
||||
"show_undo_redo_buttons": true,
|
||||
"default_custom_shirt_price": 1000
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jnazQvrkCtoiC58jzKUkH1oOrCOZznjtqrHRDUUF0iQ=": {
|
||||
"name": "jnazQvrkCtoiC58jzKUkH1oOrCOZznjtqrHRDUUF0iQ=",
|
||||
"value": {
|
||||
"ios_highend_min_physical_memory": 0,
|
||||
"ios_highend_min_allocated_memory": 2100,
|
||||
"preallocate_memory_bytes": 0,
|
||||
"iosLowFPS": 60,
|
||||
"iosHighFPS": 60,
|
||||
"resolution_check_for_quality_enabled": false,
|
||||
"resolution_check_for_quality_pixel_count": 2073600,
|
||||
"quarter_imposter_resolution_enabled": false,
|
||||
"quarter_imposter_resolution_divisor": 1,
|
||||
"reduce_imposter_size_divisor": 1,
|
||||
"imposter_format": "ARGB32",
|
||||
"partial_fallback_fonts": false
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"allocated_experiment_name": "JFMP4GkWyPaBJUWar1N1DHstqh/k1tx51yt6zQ2TDH0=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"iosHighFPS"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ZKO7bq5psuJSKEfaeZ8YiTyyz3K5h7MqmRWEawaoB30=": {
|
||||
"name": "ZKO7bq5psuJSKEfaeZ8YiTyyz3K5h7MqmRWEawaoB30=",
|
||||
"value": {
|
||||
"is_rrui": false,
|
||||
"slot_count_non_rrplus": 18,
|
||||
"card_prefab_name": "large"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"m3PmmvaIYT1PKl0mdIEU45kuu7aeTf7u4Z7muanf3co=": {
|
||||
"name": "m3PmmvaIYT1PKl0mdIEU45kuu7aeTf7u4Z7muanf3co=",
|
||||
"value": {
|
||||
"third_person_button_variant": "LARGE_LIVE_TQ",
|
||||
"third_person_button_icon": "EYE",
|
||||
"third_person_button_layout": "LARGE_RADIAL"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"5aYKjIdHHgEJA9n+NDgHKVvg75gZCqPOVxV/LLL8xY8=": {
|
||||
"name": "5aYKjIdHHgEJA9n+NDgHKVvg75gZCqPOVxV/LLL8xY8=",
|
||||
"value": {
|
||||
"use_server_thumbnail": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"JBznafBX64EjyC6lbkdbFF+oonH3Jdy4uaZHDO9hn80=": {
|
||||
"name": "JBznafBX64EjyC6lbkdbFF+oonH3Jdy4uaZHDO9hn80=",
|
||||
"value": {
|
||||
"CuratedList_PlayCategories": "Discovery.PageSource.PlayCategories"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"YGESgHonOG0k0HDGANH9HivdzsfDovB3yUz4wacK1Dg=": {
|
||||
"name": "YGESgHonOG0k0HDGANH9HivdzsfDovB3yUz4wacK1Dg=",
|
||||
"value": {
|
||||
"useAlternateAvatar": true,
|
||||
"useNewLayout": true,
|
||||
"showOnlineFriends": true,
|
||||
"onlineFriendsViewAll": true,
|
||||
"newCallToAction": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"VZfwICo6oLwvW2mMhgTAFyW/Dqasb8M6IDsS4rYhutI=": {
|
||||
"name": "VZfwICo6oLwvW2mMhgTAFyW/Dqasb8M6IDsS4rYhutI=",
|
||||
"value": {
|
||||
"IsSearchWithTagsEnabled": true,
|
||||
"UseElasticsearch": true,
|
||||
"UseSecondaryOrdering": true,
|
||||
"UseFuzzySearch": true,
|
||||
"SearchDescription": false,
|
||||
"NameRelevanceBoost": 1,
|
||||
"TagRelevanceBoost": 1,
|
||||
"DescriptionRelevanceBoost": 1
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"lLheVmeR3kwLKGDpKPNtO+GVEp6czRoucBbSDRKvmAI=": {
|
||||
"name": "lLheVmeR3kwLKGDpKPNtO+GVEp6czRoucBbSDRKvmAI=",
|
||||
"value": {
|
||||
"single_carousel": false,
|
||||
"separate_defaultsku_and_campaign_carousels": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"UncE/Rzl7KbtmBUBBqd1A6pgjBtEjza3wYJ7xAusnDc=": {
|
||||
"name": "UncE/Rzl7KbtmBUBBqd1A6pgjBtEjza3wYJ7xAusnDc=",
|
||||
"value": {
|
||||
"platform_specific_pickup_transforms": true,
|
||||
"beans_ignore_pickup_transform_on_thrown_objects": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"4jiXbPpYFA7AWm2YddXI3B/tMBC8J2ij9o5f/FcDqJw=": {
|
||||
"name": "4jiXbPpYFA7AWm2YddXI3B/tMBC8J2ij9o5f/FcDqJw=",
|
||||
"value": {
|
||||
"NetworkBytes": -1,
|
||||
"Rpcs": -1
|
||||
},
|
||||
"rule_id": "prestart",
|
||||
"group": "prestart",
|
||||
"allocated_experiment_name": "p2akTJAcMMMe3vH5gwVZSaHJnHzc7ooN8Axb58sCGxg=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"NetworkBytes",
|
||||
"Rpcs"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "RoomInstanceID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"0z1duawt6GnIqKBR7Pnsf4Gwfy2j1ESbqvxttcZj3eY=": {
|
||||
"name": "0z1duawt6GnIqKBR7Pnsf4Gwfy2j1ESbqvxttcZj3eY=",
|
||||
"value": {
|
||||
"enable_retain_ui_state": true,
|
||||
"show_watch_button_in_menus": false,
|
||||
"portal_buttons_act_like_tabs": false,
|
||||
"portal_always_present": false,
|
||||
"enable_retain_cursor_position": true,
|
||||
"retain_ui_state_duration": 60,
|
||||
"use_dir_nav": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"/S3ABRJfD4E3NnyBZv3jjZWP0+rL2YxDpyiX3P1dXlI=": {
|
||||
"name": "/S3ABRJfD4E3NnyBZv3jjZWP0+rL2YxDpyiX3P1dXlI=",
|
||||
"value": {
|
||||
"ButtonDisplayOrder": [
|
||||
"tryon",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"ShowRRPlusButton": false,
|
||||
"ShowRecommendedForYouButton": false,
|
||||
"ShowTokenStore": false,
|
||||
"ShowCustomClothingButton": false,
|
||||
"ShowDormMirrorPurchaseCampaignButton": true,
|
||||
"DormMirrorPurchaseCampaignID": 284,
|
||||
"ShowStoreBadgeNotification": true,
|
||||
"show_shopping_bag_on_add": false,
|
||||
"ShowTryOnButton": true,
|
||||
"TryOnButtonText": "Try Outfit",
|
||||
"TryOnImageIndex": 0
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "3817046945",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "3817046945",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"7JeIVehYDNfWvIVpvcu27x9crQBmjjoQ2ENdGr8e7FY=": {
|
||||
"name": "7JeIVehYDNfWvIVpvcu27x9crQBmjjoQ2ENdGr8e7FY=",
|
||||
"value": {
|
||||
"CuratedList_AppNavPortalPanel": "Discovery.PageSource.AppNavPortalPanel"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"c3IjqET3ZYVrWA572c2oqC6WYk03Vq3QskKPr4B85jg=": {
|
||||
"name": "c3IjqET3ZYVrWA572c2oqC6WYk03Vq3QskKPr4B85jg=",
|
||||
"value": {
|
||||
"blob_shadow_enabled": true,
|
||||
"force_high_quality": false,
|
||||
"test_config": "QRP GDRP GRP APPLY"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1195813645",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1195813645",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"9CAONkgaaxzNHZL5EiG/Adg4QbLWjxziCVNYs5DsWCk=": {
|
||||
"name": "9CAONkgaaxzNHZL5EiG/Adg4QbLWjxziCVNYs5DsWCk=",
|
||||
"value": {
|
||||
"CuratedList_PlayCategories": "Discovery.PageSource.PlayCategories"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"6vTdDlVzfAgkng6dM3CSJWDAnQQDAbR9YvtQgwZ2omY=": {
|
||||
"name": "6vTdDlVzfAgkng6dM3CSJWDAnQQDAbR9YvtQgwZ2omY=",
|
||||
"value": {
|
||||
"target_orientation_room_name": "SocialOrientation",
|
||||
"orientation_sub_room_names": [
|
||||
"LandingZone",
|
||||
"WelcomeCenter_2_SC2",
|
||||
"PracticeGym_2",
|
||||
"RewardsRoom_2_SC2"
|
||||
],
|
||||
"orientation_version": 2,
|
||||
"test_roomatars": true,
|
||||
"test_videos": true,
|
||||
"canned_chat_room_mapping": {
|
||||
"SocialOrientation": [
|
||||
"Retention.CannedChat.SocialOrientationA",
|
||||
"Retention.CannedChat.SocialOrientationB",
|
||||
"-",
|
||||
"-"
|
||||
]
|
||||
},
|
||||
"supports_identity_creation": false,
|
||||
"allow_skip_orientation": false,
|
||||
"prefetch_asset_bundles": true,
|
||||
"target_progression": [],
|
||||
"is_giftshop": false,
|
||||
"token_inflation_amount": 0,
|
||||
"storefront_type": "",
|
||||
"upsell_amount": 0,
|
||||
"red_icon_when_muted": false,
|
||||
"preload_scene": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "1778358864",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3897177887",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
},
|
||||
{
|
||||
"gate": "1778358864",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"l2RY/XgQsxyQyYkpIy1bKEOO0r0KNyArBaSIRisW+gs=": {
|
||||
"name": "l2RY/XgQsxyQyYkpIy1bKEOO0r0KNyArBaSIRisW+gs=",
|
||||
"value": {
|
||||
"new_out_of_token_ui": true,
|
||||
"OutOfTokenSkuIds": [
|
||||
2,
|
||||
5,
|
||||
8,
|
||||
9,
|
||||
160,
|
||||
177,
|
||||
178,
|
||||
179,
|
||||
180
|
||||
],
|
||||
"ShowNewOutOfTokenPaths": true,
|
||||
"PopUpShopUsesRRUIFlow": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"2hDudG3hy1zl8yPtGe0lIq66RLIKZXZLP5u/k0CjM7Y=": {
|
||||
"name": "2hDudG3hy1zl8yPtGe0lIq66RLIKZXZLP5u/k0CjM7Y=",
|
||||
"value": {
|
||||
"GroupName": "",
|
||||
"workflow_variant": ""
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Pf7SF4NCjIQFPSv3qBYu/cJhPJBfAPkCJLIe8IcFyWc=": {
|
||||
"name": "Pf7SF4NCjIQFPSv3qBYu/cJhPJBfAPkCJLIe8IcFyWc=",
|
||||
"value": {
|
||||
"enable_discoverability": false,
|
||||
"enable_party_private_instance": false,
|
||||
"Lifecycle.PSF.EnableAllChatVoiceCalls": false,
|
||||
"Tip:Visibility": 0
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ZkFnbU3+/RiMD7616JSFMghK3vHFVV/evZHRTw4lkfM=": {
|
||||
"name": "ZkFnbU3+/RiMD7616JSFMghK3vHFVV/evZHRTw4lkfM=",
|
||||
"value": {
|
||||
"IsCustomDormForNewUsersEnabled": false,
|
||||
"IsCustomDormCustomizationLocked": false,
|
||||
"ShowNewTutorial": false,
|
||||
"ShowTutorialOnSpawn": false,
|
||||
"IsDormSlotsVisible": true,
|
||||
"ShowDiscoveryNotification": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"WeumZrvBRlaktNW0Wv946N8015M72ez8ZZ4ISSzMOzA=": {
|
||||
"name": "WeumZrvBRlaktNW0Wv946N8015M72ez8ZZ4ISSzMOzA=",
|
||||
"value": {
|
||||
"show_ccu_count": false,
|
||||
"show_popular_badge": true,
|
||||
"pin_popular_room": false,
|
||||
"supported_carousels": [
|
||||
"SEARCH_ROOMS_WATCH"
|
||||
],
|
||||
"Discovery.CCUIconThresholds": "[ { \"Count\": 50, \"Label\": \"Hot\", \"Color\": \"#EA6F48\" } ]",
|
||||
"Discovery.PopularBadgeCCUThreshold": 50,
|
||||
"best_popular_card_threshold": -1,
|
||||
"room_list_join_or_create_count": 0,
|
||||
"room_list_join_only_count": 0
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"EJRYalGgXA7TtEUz3ul5kA2pT+MXzPo6sHP96Y/JCZA=": {
|
||||
"name": "EJRYalGgXA7TtEUz3ul5kA2pT+MXzPo6sHP96Y/JCZA=",
|
||||
"value": {
|
||||
"use_follow_constraint": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"owDmjZx4q7Y1zkyBk85kmtEvREB47p1CjtR3dqg0IW4=": {
|
||||
"name": "owDmjZx4q7Y1zkyBk85kmtEvREB47p1CjtR3dqg0IW4=",
|
||||
"value": {
|
||||
"can_gift_custom_shirts": true,
|
||||
"can_write_gift_message": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"f9BKXHWszsCrJzDk+4qXXDiryFpZvbuqu35EeKbAjNQ=": {
|
||||
"name": "f9BKXHWszsCrJzDk+4qXXDiryFpZvbuqu35EeKbAjNQ=",
|
||||
"value": {
|
||||
"is_customize_HUD_on": true,
|
||||
"customize_icon_mirror": "new",
|
||||
"hide_unowned_items": true,
|
||||
"show_link_to_store": true,
|
||||
"merge_custom_and_fp_shirts": true,
|
||||
"show_featured_store_row": true,
|
||||
"featured_item_algo_endpoint": "itemsforyou",
|
||||
"has_more_customization_access_points": true,
|
||||
"default_customize_tab_name": "Tops",
|
||||
"improve_ui": true,
|
||||
"add_token_counter": false,
|
||||
"use_server_thumbnail": false,
|
||||
"show_look_outfit_seperation_toast": false,
|
||||
"improved_categorization": false,
|
||||
"use_store_button": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"QFm3Ilr9QT9ilQzEsoRFpZWONLHnpfS/WFfy4KM6v00=": {
|
||||
"name": "QFm3Ilr9QT9ilQzEsoRFpZWONLHnpfS/WFfy4KM6v00=",
|
||||
"value": {
|
||||
"algo_name": "text-embedding-004",
|
||||
"algo_version": "0",
|
||||
"return_sorted_by_offline_ranking": true,
|
||||
"number_of_entities_to_return": 100,
|
||||
"minimum_offline_ranking_score": 20,
|
||||
"minimum_similarity_score": 0,
|
||||
"embedding_dimension": 768
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"9JY2wO936pjcaXjzlyJDSMlP1ZkMayTuW3sLwb30Kdc=": {
|
||||
"name": "9JY2wO936pjcaXjzlyJDSMlP1ZkMayTuW3sLwb30Kdc=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "single",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": true,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"RnYDwRtP7FJYzD/qidiQqnuIp/VVWXUHuX/0RMA0FCw=": {
|
||||
"name": "RnYDwRtP7FJYzD/qidiQqnuIp/VVWXUHuX/0RMA0FCw=",
|
||||
"value": {
|
||||
"ShowSearchIcon": false,
|
||||
"ShowSearchButton": false,
|
||||
"ShowSearchBar": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Doo8gNfFNr2obCa0BHi6V9GTFLgTWksc2XOQGPL/Wpc=": {
|
||||
"name": "Doo8gNfFNr2obCa0BHi6V9GTFLgTWksc2XOQGPL/Wpc=",
|
||||
"value": {
|
||||
"use_unified_room_store": true,
|
||||
"show_all_category_room_store": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"KERbXBXADpLh1FqjBzCIaVyGWvLYkdqWq6EsHGE57/I=": {
|
||||
"name": "KERbXBXADpLh1FqjBzCIaVyGWvLYkdqWq6EsHGE57/I=",
|
||||
"value": {},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"L2XVPTA9dzR+4QYnAKeVHPpPj/4oeN5qA/YsDF3dn80=": {
|
||||
"name": "L2XVPTA9dzR+4QYnAKeVHPpPj/4oeN5qA/YsDF3dn80=",
|
||||
"value": {
|
||||
"use_curated_list": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"A+KQdJwr7orOL8bPluthLGDg+tlrNGKCrgW6K3LDzN8=": {
|
||||
"name": "A+KQdJwr7orOL8bPluthLGDg+tlrNGKCrgW6K3LDzN8=",
|
||||
"value": {
|
||||
"card_size": "large",
|
||||
"hidden_types": [],
|
||||
"display_order": [],
|
||||
"display order": {},
|
||||
"isPromptingLowestTokenPackage": false,
|
||||
"prompt_lowest_token_package": false,
|
||||
"only_prompt_non_payer": true,
|
||||
"hide_token_store": false,
|
||||
"hide_buy_token_and_items": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"LKgjAbl/cQAF/4a7+3C0qDbdNdabF5T1OeWUku+USlE=": {
|
||||
"name": "LKgjAbl/cQAF/4a7+3C0qDbdNdabF5T1OeWUku+USlE=",
|
||||
"value": {
|
||||
"remove_vr_input_configs": false,
|
||||
"remove_laser_teleporter": false,
|
||||
"lazy_load_objective_log_hud": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"oJnDNB7fvlNn8xLcBuk1vqVGy2U25Sr1k37o2yMapzU=": {
|
||||
"name": "oJnDNB7fvlNn8xLcBuk1vqVGy2U25Sr1k37o2yMapzU=",
|
||||
"value": {
|
||||
"modern_bean": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"S/5u8uwkxaOR8oz40x4vF89vXu4NJeKrXWsY65+r2QE=": {
|
||||
"name": "S/5u8uwkxaOR8oz40x4vF89vXu4NJeKrXWsY65+r2QE=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "single",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": true,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ZMBIhNrExG8+KanKbryxJO2a15C//1qd7Tx/go5oQx0=": {
|
||||
"name": "ZMBIhNrExG8+KanKbryxJO2a15C//1qd7Tx/go5oQx0=",
|
||||
"value": {},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ie3iFzG1joEU/efeOiDOj9KAyG2xrEh7u9WcdP+v5jE=": {
|
||||
"name": "ie3iFzG1joEU/efeOiDOj9KAyG2xrEh7u9WcdP+v5jE=",
|
||||
"value": {
|
||||
"show_search_tips": true,
|
||||
"show_similar_rooms_carousel": false,
|
||||
"show_custom_carousel": false,
|
||||
"use_legacy_null_search": false,
|
||||
"autosearch_disabled": false,
|
||||
"hide_filters": true,
|
||||
"show_history_with_chips": false,
|
||||
"show_history_with_button": false,
|
||||
"show_history": false,
|
||||
"show_search_rooms_text_button": true,
|
||||
"show_room_size_filter": false,
|
||||
"enable_autocomplete": true,
|
||||
"options_count_per_platform": {
|
||||
"mobile": 4,
|
||||
"pc": 4,
|
||||
"vr": 4
|
||||
},
|
||||
"AutoCompleteIndexName": "room-autocomplete_inc_popular_rooms"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"CdLJeNltHjtYLKxTasBqIka66U2OQS+udhCEFOhv/zA=": {
|
||||
"name": "CdLJeNltHjtYLKxTasBqIka66U2OQS+udhCEFOhv/zA=",
|
||||
"value": {
|
||||
"force_besthttp_typeload": false,
|
||||
"delay_recnet_connect": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": true,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "stableID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ore8Z5ilzegUkq0xf5s3hEKgedKzt+z7onvlDjTRrOg=": {
|
||||
"name": "ore8Z5ilzegUkq0xf5s3hEKgedKzt+z7onvlDjTRrOg=",
|
||||
"value": {
|
||||
"slowdown_duration_ms": 0,
|
||||
"hitch_interval_ms": 0,
|
||||
"hitch_duration_ms": 0,
|
||||
"maintain_fps": 0
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"YSmK7hit2kTUkn1Upm1Z8EDy3S2BCMHOI0utn8TvkRI=": {
|
||||
"name": "YSmK7hit2kTUkn1Upm1Z8EDy3S2BCMHOI0utn8TvkRI=",
|
||||
"value": {
|
||||
"hide_ui": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"8DESjtWwY/VNFfWoFWPqyYCKF0fpsTFOVgSJE4qJmf4=": {
|
||||
"name": "8DESjtWwY/VNFfWoFWPqyYCKF0fpsTFOVgSJE4qJmf4=",
|
||||
"value": {
|
||||
"invite_friends_for_free_room_joins": true,
|
||||
"invite_friends_for_invites": false,
|
||||
"show_rrui_notification_dialogs": true,
|
||||
"CreateInstance": true,
|
||||
"IsPublic": true,
|
||||
"show_rrui_game_invite_dialog": false,
|
||||
"show_decline_responses_game_invite_dialog": false,
|
||||
"show_rrui_announcements": true,
|
||||
"show_notification_for_new_friend_online": false,
|
||||
"new_friend_notification_quick_action_prompt": "",
|
||||
"ignore_version_check_for_invite_button": true,
|
||||
"notify_invitee_to_update_game": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"ZASLgU152A/wRnlwHEpAd/Ksh+dMT3venXGhSPk+WXI=": {
|
||||
"name": "ZASLgU152A/wRnlwHEpAd/Ksh+dMT3venXGhSPk+WXI=",
|
||||
"value": {
|
||||
"is_enabled": true,
|
||||
"notice_on_visit_probability": 0.5,
|
||||
"notice_duration_seconds": 2,
|
||||
"message_string": "Voice moderation is active in this room.",
|
||||
"title_string": "Coach is listening!",
|
||||
"moderated_player_only": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"/pH+3mMVH4drpPjZEpPIXxVgBfCUfGVhxriKl0W6b/k=": {
|
||||
"name": "/pH+3mMVH4drpPjZEpPIXxVgBfCUfGVhxriKl0W6b/k=",
|
||||
"value": {
|
||||
"pug_max_result_surfacing": 0,
|
||||
"pug_volume_multiplier": 0.75
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"dOy9hRviOeyImCHXP66pacYwvcGixQsMuqCkSrNA6TA=": {
|
||||
"name": "dOy9hRviOeyImCHXP66pacYwvcGixQsMuqCkSrNA6TA=",
|
||||
"value": {
|
||||
"dorm_mirror_store_button_position": -1,
|
||||
"dorm_mirror_button_display_order": [],
|
||||
"show_store_button": false,
|
||||
"ShrinkButtons": false,
|
||||
"store_item_prefab_variant_name": "small",
|
||||
"hide_rarity": false,
|
||||
"hide_item_label": false,
|
||||
"hide_nav_bar": false,
|
||||
"use_buy_cta_button": false,
|
||||
"CuratedList_StoreFeatured": "Discovery.PageSource.StoreFeatured",
|
||||
"CuratedList_StoreClothing": "Discovery.PageSource.StoreClothing",
|
||||
"show_prediscount_price": false,
|
||||
"hide_prediscount_price": false,
|
||||
"use_categorization_improvements_store": "False",
|
||||
"ItemDetailsVariantPrefabName": "",
|
||||
"AvatarPreviewSize": 512,
|
||||
"use_mixed_carousel_for_store_categories": false,
|
||||
"use_categorization_improvements_store_v2": true,
|
||||
"UseVNextItemDetailPages": true,
|
||||
"UseAlgorithmicDisplayPriority": false,
|
||||
"force_no_horizontal_scroll": true,
|
||||
"CuratedList_StoreInventions": "Discovery.PageSource.StoreInventions",
|
||||
"Control": "Discovery.PageSource.StoreInventions",
|
||||
"use_data_bound_store_item_card": false,
|
||||
"cai_search_logging_on": true,
|
||||
"Econ.Store.Categories": "",
|
||||
"CuratedList_UnifiedTops": "Discovery.PageSource.UnifiedTops",
|
||||
"CuratedList_StoreAppearance": "Discovery.PageSource.StoreAppearance",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_StoreCategories": "Discovery.PageSource.StoreCategories",
|
||||
"CuratedList_StoreAccessories": "Discovery.PageSource.StoreAccessories",
|
||||
"CuratedList_StoreConsumables": "Discovery.PageSource.StoreConsumables",
|
||||
"CuratedList_StoreOrientation": "Discovery.PageSource.StoreOrientation",
|
||||
"CuratedList_StoreStyling": "Discovery.PageSource.StoreStyling",
|
||||
"CuratedList_StoreDeals": "Discovery.PageSource.StoreDeals",
|
||||
"OrderedTokenBundleIds": []
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nHL59I3GhhHLogtMlLCkbxjkBJx+eZJXuh/bbdqvzw8=": {
|
||||
"name": "nHL59I3GhhHLogtMlLCkbxjkBJx+eZJXuh/bbdqvzw8=",
|
||||
"value": {
|
||||
"show_reactions": true,
|
||||
"show_emotes": true,
|
||||
"reactions": [
|
||||
"👍",
|
||||
"❤️",
|
||||
"🎉",
|
||||
"😭:Sad",
|
||||
"👎",
|
||||
"😲:Shocked",
|
||||
"💀",
|
||||
"🤣:Laugh"
|
||||
],
|
||||
"emote_hud_icon_override": "",
|
||||
"hud_element_priority": [],
|
||||
"show_react_gesture_tip": false,
|
||||
"fade_react_joystick": false,
|
||||
"show_react_joystick": false,
|
||||
"react_gesture_tip": "",
|
||||
"show_react_radial_joystick": false,
|
||||
"custom_local_profile": true,
|
||||
"show_profile_blocklist": false,
|
||||
"hide_old_set_badges": true,
|
||||
"Tip:Visibility": 1,
|
||||
"snowball_enabled": false,
|
||||
"snowball_feedback_sizing": false,
|
||||
"snowball_feedback_vfx": false,
|
||||
"snowball_feedback_room_chat": false,
|
||||
"snowball_feedback_notification": false
|
||||
},
|
||||
"rule_id": "6QgSAFFJwBxslkhuBZzOzu:override",
|
||||
"group": "6QgSAFFJwBxslkhuBZzOzu:override",
|
||||
"group_name": "Test",
|
||||
"allocated_experiment_name": "LWs5t1Wg2k2BmtK+sLqEJzQKEdU9J3buYMSHebp1Igc=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"custom_local_profile",
|
||||
"show_profile_blocklist",
|
||||
"hide_old_set_badges",
|
||||
"show_reactions",
|
||||
"show_emotes",
|
||||
"reactions",
|
||||
"Tip:Visibility"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"kZR5q9i8kYU9Ky3SpnKfDvkMp4fTwSnCZZ4PsGFLQfA=": {
|
||||
"name": "kZR5q9i8kYU9Ky3SpnKfDvkMp4fTwSnCZZ4PsGFLQfA=",
|
||||
"value": {
|
||||
"use_app_nav": false,
|
||||
"prevent_watch_home": false,
|
||||
"CuratedList_AppNavWidgetPanel": "Discovery.PageSource.AppNavWidgetPanel",
|
||||
"use_app_nav_forship": false,
|
||||
"enable_app_nav_tutorial": false,
|
||||
"enable_retain_ui_state": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"laf6rmJUUAlGD9ciogV2gCFxf4MpuwqxcYkvx9PpKcE=": {
|
||||
"name": "laf6rmJUUAlGD9ciogV2gCFxf4MpuwqxcYkvx9PpKcE=",
|
||||
"value": {
|
||||
"has_button": true,
|
||||
"has_dormskin_tab": false,
|
||||
"has_qol": true,
|
||||
"has_warning_banner": true,
|
||||
"Econ.EnableTryingBundleItems": "",
|
||||
"Econ.MikeTestConfigValue": "",
|
||||
"prefab_variant_name": "",
|
||||
"store_item_prefab_variant_name": "default",
|
||||
"invention_prefab_variant_name": "default",
|
||||
"custom_avatar_item_prefab_variant_name": "default",
|
||||
"exclude_owned": false,
|
||||
"ad_carousel_prefab_variant_name": "default",
|
||||
"use_unified_avatar_item_search": true,
|
||||
"UGC_search_text_title": "Custom Clothing",
|
||||
"RRO_items_search_title": "Backpack Items",
|
||||
"use_new_filter_ordering": true,
|
||||
"look_up_gift_drop_from_local_cache": true,
|
||||
"CAI_search_includes_coach_items": false,
|
||||
"new_outfit_page_logic": false,
|
||||
"gift_button_in_first_position_for_owned_item": false,
|
||||
"gift_button_is_primary_button": false,
|
||||
"equip_button_is_primary_button": true,
|
||||
"is_equip_item_in_store_enabled": true,
|
||||
"extra_purchasable_item_ids_in_default_search": [
|
||||
541,
|
||||
538,
|
||||
539
|
||||
],
|
||||
"cai_default_ordering": "SearchScoreDescending"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"0QZXaN7i+cATTD+NjMQomSHOaGznrdRIDHMPoCth1Q4=": {
|
||||
"name": "0QZXaN7i+cATTD+NjMQomSHOaGznrdRIDHMPoCth1Q4=",
|
||||
"value": {
|
||||
"canned_chat_RecRoomDefault": [
|
||||
"Hi!",
|
||||
"Bye!",
|
||||
"Ready?",
|
||||
"BRB",
|
||||
"Let’s go!"
|
||||
],
|
||||
"canned_chat_RoomDefault": [
|
||||
"Come with me",
|
||||
"Help!",
|
||||
"Let's party up!",
|
||||
"Let's be friends!",
|
||||
"May I pick the room?"
|
||||
],
|
||||
"canned_chat_CustomA": [
|
||||
"Yes",
|
||||
"OK",
|
||||
"Thanks",
|
||||
"LOL",
|
||||
"You look great!"
|
||||
],
|
||||
"canned_chat_CustomB": [
|
||||
"No",
|
||||
"Boo",
|
||||
"Sorry",
|
||||
"I don’t like this",
|
||||
"What the coach?!"
|
||||
],
|
||||
"room_chat_bubble_use_revamped_ui": true,
|
||||
"room_chat_bubble_font_size_mult": 1.5,
|
||||
"room_chat_bubble_bg_color": "#ffffffaa",
|
||||
"room_chat_bubble_text_color": "black",
|
||||
"room_chat_bubble_max_emoji_embiggen_len": 1,
|
||||
"room_chat_bubble_embiggen_mult": 2,
|
||||
"room_chat_bubble_should_embolden": true,
|
||||
"room_chat_bubble_text_alpha": [
|
||||
1,
|
||||
0.8,
|
||||
0.6
|
||||
],
|
||||
"room_chat_bubble_bg_alpha": [
|
||||
0.66,
|
||||
0.5,
|
||||
0.3
|
||||
],
|
||||
"room_chat_canned_detached_mode": "right",
|
||||
"show_party_chat_in_room_chat_menu": true,
|
||||
"enable_party_voice_toggle_in_room_chat": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"1ejmhXj+aES/b87GQZko6NBAUzkdn3DNpbK6iJc8xgI=": {
|
||||
"name": "1ejmhXj+aES/b87GQZko6NBAUzkdn3DNpbK6iJc8xgI=",
|
||||
"value": {
|
||||
"UGC.CircuitsV2.Limit.Compute": "",
|
||||
"UGC.CircuitsV2.Limit.Compute.RollingWindow": "",
|
||||
"UGC.CircuitsV2.Limit.Compute.RollingWindowSize": ""
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "RoomInstanceID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"lUbMEJcnku4LLKspzvXnP/iEGiy8CWBU8P/w+B1WPpo=": {
|
||||
"name": "lUbMEJcnku4LLKspzvXnP/iEGiy8CWBU8P/w+B1WPpo=",
|
||||
"value": {
|
||||
"new_watch_architecture_enabled": true,
|
||||
"rrui_notifications_enabled": true,
|
||||
"rrui_people_menu_enabled": true,
|
||||
"new_portfolio_page_enabled": true,
|
||||
"banner_none": false,
|
||||
"banner_featured": false,
|
||||
"banner_hot": false,
|
||||
"banner_recent": false,
|
||||
"home_banner": "none",
|
||||
"home_search_bar_enabled": false,
|
||||
"home_larger_main_menu_enabled": false,
|
||||
"carousel_order": [
|
||||
"Rooms_ContinuePlaying",
|
||||
"Rooms_FriendsArePlaying",
|
||||
"Rooms_Hot",
|
||||
"Rooms_RRO",
|
||||
"Items_Featured",
|
||||
"Items_FeaturedCustom",
|
||||
"Inventions_Featured",
|
||||
"Inventions_FeaturedDormSkins",
|
||||
"Events_My",
|
||||
"Clubs_My",
|
||||
"Clubs_Suggested",
|
||||
"Top5"
|
||||
],
|
||||
"home_play_button_alt_icon_enabled": false,
|
||||
"Growth.ForcedPostOrientationCategorySelectDuration": "",
|
||||
"GroupName": "",
|
||||
"play_tab_enabled": true,
|
||||
"new_home_icons_enabled": true,
|
||||
"new_home_layout_enabled": true,
|
||||
"in_icons_or_layout_experiment": false,
|
||||
"browser_tabs_preserve_history_enabled": false,
|
||||
"play_button_enabled": true,
|
||||
"show_nav_bar_on_standalone_context_pages": true,
|
||||
"capitalize_navbar_tabs": false,
|
||||
"use_new_people_card": true,
|
||||
"hide_large_people_search_bar": false,
|
||||
"friends_default_tab": false,
|
||||
"friends_subtab": false,
|
||||
"use_new_people_card_invite_variant": false,
|
||||
"depth_multiplier": 1,
|
||||
"router_prefill_group": "",
|
||||
"fallback_router_prefill_group": "",
|
||||
"router_enable_prefill_routes": false,
|
||||
"router_override_hidden_page_pool_size": true,
|
||||
"router_hidden_page_pool_size": 0,
|
||||
"create_tab_enabled": true,
|
||||
"store_tab_enabled": true,
|
||||
"NoNavBar": true,
|
||||
"expand_vr_nav_bar": true,
|
||||
"copy_room_button_text": "Make Your Own",
|
||||
"open_idp_from_backpack": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"zZQcn3QyoUxpWtv//0dT/AMBuiByP5ckNABX/eOOais=": {
|
||||
"name": "zZQcn3QyoUxpWtv//0dT/AMBuiByP5ckNABX/eOOais=",
|
||||
"value": {
|
||||
"avatar_scalability_active": false,
|
||||
"lod0_budget": 10000,
|
||||
"lod1_budget": 10000,
|
||||
"updates_wait_for_empty_build_queue": false,
|
||||
"use_dynamic_budgets": false,
|
||||
"dynamic_budget_mb": 10000,
|
||||
"dynamic_budget_lod0_cost_mb": 5,
|
||||
"dynamic_budget_lod1_cost_mb": 4,
|
||||
"dynamic_budget_lod2_cost_mb": 0.4,
|
||||
"quest_bean_job_builds": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "294724160",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "294724160",
|
||||
"gateValue": "false",
|
||||
"ruleID": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jMzFsBPJc7gt4OdCnBju9ZWF0+JkWXi1BzbsxxWyYQ8=": {
|
||||
"name": "jMzFsBPJc7gt4OdCnBju9ZWF0+JkWXi1BzbsxxWyYQ8=",
|
||||
"value": {
|
||||
"sync_parameter": 0
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"WmqmuKUeHiXaof0xFAThI/UKY/lfBGLp5LzWs4Djawg=": {
|
||||
"name": "WmqmuKUeHiXaof0xFAThI/UKY/lfBGLp5LzWs4Djawg=",
|
||||
"value": {
|
||||
"can_give": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"kb6OnsnnnK/v8I3IXeR37CeiQEWXg2EHne4UtD+/WmU=": {
|
||||
"name": "kb6OnsnnnK/v8I3IXeR37CeiQEWXg2EHne4UtD+/WmU=",
|
||||
"value": {
|
||||
"PurchaseReminderPrefabVariantOverride": "single",
|
||||
"UseBackgroundImageForPurchaseReminderOverride": true,
|
||||
"Override_Content_Json": ""
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"mKvYb3bnbiEZHgYaRrGEAh0kErP65qS6dwvtwxcd/Eg=": {
|
||||
"name": "mKvYb3bnbiEZHgYaRrGEAh0kErP65qS6dwvtwxcd/Eg=",
|
||||
"value": {
|
||||
"new_out_of_token_ui": "true",
|
||||
"ShowNewOutOfTokenPaths": "true"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"BjN+2Lwh6e6m9tkqrMaNywbpSrzPw5zgk5tOjApKHH0=": {
|
||||
"name": "BjN+2Lwh6e6m9tkqrMaNywbpSrzPw5zgk5tOjApKHH0=",
|
||||
"value": {
|
||||
"can_see_player_card": false,
|
||||
"can_see_rrp_card": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1778358864",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1778358864",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"YbGu5Y+DwnC+bKGG/MXcaFjm034kblL/GyNPi6/qaF0=": {
|
||||
"name": "YbGu5Y+DwnC+bKGG/MXcaFjm034kblL/GyNPi6/qaF0=",
|
||||
"value": {
|
||||
"algo_name": "Rooms_2025JulGameAI",
|
||||
"algo_version": "1",
|
||||
"number_of_entities_to_shuffle": 6
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"G8+lH2CSWe0yhtnAvj2DjCVb7jfu8+E7Y5NZz0Hc53w=": {
|
||||
"name": "G8+lH2CSWe0yhtnAvj2DjCVb7jfu8+E7Y5NZz0Hc53w=",
|
||||
"value": {
|
||||
"remove_dance": false,
|
||||
"remove_reach": false,
|
||||
"remove_emote": false,
|
||||
"remove_emotes_from_room_chat": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Oy2tJrRNQbHBP+Hc9e38J6CkMg2an2NgydLi/LFC50M=": {
|
||||
"name": "Oy2tJrRNQbHBP+Hc9e38J6CkMg2an2NgydLi/LFC50M=",
|
||||
"value": {
|
||||
"tap_player_radial_menu_variant": "TEST1"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"sWjdDjboZgs9DGTTXbg9HWZdQPBUk53xj2+OmCwIf7A=": {
|
||||
"name": "sWjdDjboZgs9DGTTXbg9HWZdQPBUk53xj2+OmCwIf7A=",
|
||||
"value": {
|
||||
"IsNewAccountDefaultMuted": false,
|
||||
"RedIconWhenMuted": false,
|
||||
"RedIconWhenSpeakingAndMuted": false,
|
||||
"HighlightWhenMutedAndOtherPlayersTalking": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"7F09o3xgb69YVwZbF51P5U+Ki8oEij3A0xapPGTqF50=": {
|
||||
"name": "7F09o3xgb69YVwZbF51P5U+Ki8oEij3A0xapPGTqF50=",
|
||||
"value": {
|
||||
"Retention.Coordinate.BeaconMVPEnabled": true,
|
||||
"Tip:Visibility": 1,
|
||||
"Retention.Coordinate.ReceiveAnnouncement": true
|
||||
},
|
||||
"rule_id": "2HodNrKbmztrxVhiupQYlb:override",
|
||||
"group": "2HodNrKbmztrxVhiupQYlb:override",
|
||||
"group_name": "Test",
|
||||
"allocated_experiment_name": "7bbJm8jSbS2Y0DrQo/KwrndfF0zfVUGafmEfid/dFJM=",
|
||||
"is_device_based": false,
|
||||
"is_experiment_active": false,
|
||||
"explicit_parameters": [
|
||||
"Retention.Coordinate.BeaconMVPEnabled",
|
||||
"Tip:Visibility",
|
||||
"Retention.Coordinate.ReceiveAnnouncement"
|
||||
],
|
||||
"is_user_in_experiment": false,
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "3396355483",
|
||||
"gateValue": "true",
|
||||
"ruleID": "VQecv3QkDzY7riOlGpWDg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"3thwG0zRIuOYHS3tuBvduWbWdV2NfW74v2Igibyrtug=": {
|
||||
"name": "3thwG0zRIuOYHS3tuBvduWbWdV2NfW74v2Igibyrtug=",
|
||||
"value": {
|
||||
"NetworkBytes": -1,
|
||||
"Rpcs": -1
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "RoomID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"1Q7oKhZqth79nLPgrBeEBsyoUJHbd5FSqvdRaf9Tfes=": {
|
||||
"name": "1Q7oKhZqth79nLPgrBeEBsyoUJHbd5FSqvdRaf9Tfes=",
|
||||
"value": {
|
||||
"show_party_card": true,
|
||||
"allow_goto_private_instance": true,
|
||||
"show_leave_party_button_on_room_change": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"sE/BLF1jLGH10UgqrLiVCz4OYbA727PTU+KIhf6tQhI=": {
|
||||
"name": "sE/BLF1jLGH10UgqrLiVCz4OYbA727PTU+KIhf6tQhI=",
|
||||
"value": {
|
||||
"CuratedList_RecRoomPlusBenefits": "Discovery.PageSource.RecRoomPlusBenefits"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"WGjh+JMS6W25IzThebpFsCsz3E1gUPQvg9lkjQnFSc8=": {
|
||||
"name": "WGjh+JMS6W25IzThebpFsCsz3E1gUPQvg9lkjQnFSc8=",
|
||||
"value": {
|
||||
"show_roomie_in_hud": true,
|
||||
"show_credits_text": true,
|
||||
"show_credits_meter": false,
|
||||
"show_remote_no_credit": false,
|
||||
"campaignIds": [],
|
||||
"autospawn_energy_threshold": -1,
|
||||
"energy_mode_0_cost_scale_factor": 0.2,
|
||||
"energy_mode_1_cost_scale_factor": 1
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"iJt8UyKgwNeV43Mno7XIqBOGzB4MVo4onoZOPX/QBQE=": {
|
||||
"name": "iJt8UyKgwNeV43Mno7XIqBOGzB4MVo4onoZOPX/QBQE=",
|
||||
"value": {
|
||||
"purchase_prompt_rate_time_period": 60,
|
||||
"purchase_prompt_invokations_per_time_period": 10
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"87So08ZOpElejED5Drfa7YyQegMjFSEAES2ZPO9AvDQ=": {
|
||||
"name": "87So08ZOpElejED5Drfa7YyQegMjFSEAES2ZPO9AvDQ=",
|
||||
"value": {
|
||||
"FillCarouselWithListsService_Algorithmic_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_NewItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HatsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_HairItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_FoodItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Algorithmic_OtherItems": false,
|
||||
"FillCarouselWithListsService_Manual_FeaturedItems": false,
|
||||
"FillCarouselWithListsService_Manual_NewItems": false,
|
||||
"FillCarouselWithListsService_Manual_RRPlusItems": false,
|
||||
"FillCarouselWithListsService_Manual_FiveStarItems": false,
|
||||
"FillCarouselWithListsService_Manual_SkinsItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairDyeItems": false,
|
||||
"FillCarouselWithListsService_Manual_HatsItems": false,
|
||||
"FillCarouselWithListsService_Manual_TorsoItems": false,
|
||||
"FillCarouselWithListsService_Manual_AccessoriesItems": false,
|
||||
"FillCarouselWithListsService_Manual_GlovesItems": false,
|
||||
"FillCarouselWithListsService_Manual_HairItems": false,
|
||||
"FillCarouselWithListsService_Manual_RandomBoxesItems": false,
|
||||
"FillCarouselWithListsService_Manual_FoodItems": false,
|
||||
"FillCarouselWithListsService_Manual_PotionsItems": false,
|
||||
"FillCarouselWithListsService_Manual_KOIconsItems": false,
|
||||
"FillCarouselWithListsService_Manual_OtherItems": false,
|
||||
"canSeeBadgeNotifications": true,
|
||||
"WatchStoreIsOrderedAccordingToListsService": false,
|
||||
"WatchStoreCarouselsListPrefix": "ControlNewCustomShirtExperimentList",
|
||||
"WatchStoreFeaturedIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreClothingIsOrderedAccordingToListsService": true,
|
||||
"WatchStoreConsumablesIsOrderedAccordingToListsService": true,
|
||||
"WishlistButtonVariant": 2,
|
||||
"CanBulkGift": true,
|
||||
"FilterOutInvalidRecipients": false,
|
||||
"OrderRecipients": false,
|
||||
"RemoveOwnedItemsFromWishlist": true,
|
||||
"CanBulkPurchaseConsumables": true,
|
||||
"ShowGiftingBadgesOnItemDetails": false,
|
||||
"ShowGiftingBadgesOnStoreItemCard": false,
|
||||
"CanBulkWishlistGift": true,
|
||||
"CanBulkGiftConsumables": true,
|
||||
"IndividualBulkGiftCap": 250,
|
||||
"should_see_dormskin_button": true,
|
||||
"WatchStoreInventionsIsOrderedAccordingToListsService": false,
|
||||
"UseAlgorithmicListFeatured": false,
|
||||
"UseAlgorithmicListClothing": false,
|
||||
"UseAlgorithmicListConsumables": false,
|
||||
"UseDiscoveryServer": true,
|
||||
"ShouldPreloadDiscoverySections": true,
|
||||
"hideCancelPurchaseDialog": false,
|
||||
"skipBundleDetailsPage": false,
|
||||
"hidePlatformSKUConfirmationDialogs": false,
|
||||
"showHorizontalScrollTokenStore": true,
|
||||
"display_order": [
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 183
|
||||
},
|
||||
{
|
||||
"purchasereminderid": 135,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 10
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 11
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 161
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 164
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 9
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 8
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 5
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 2
|
||||
},
|
||||
{
|
||||
"purchasereminderid": -1,
|
||||
"skuid": 160
|
||||
}
|
||||
],
|
||||
"hidden_types": [],
|
||||
"dorm_mirror_button_display_order": [
|
||||
"rrplus",
|
||||
"dormskins",
|
||||
"customclothing",
|
||||
"store",
|
||||
"customize",
|
||||
"outfits"
|
||||
],
|
||||
"show_store_button": true,
|
||||
"shrink_buttons": true,
|
||||
"card_size": "large",
|
||||
"UseServerThumbnailsForGiftDrops": true,
|
||||
"campaignIds": [],
|
||||
"use_data_bound_store_item_card": false,
|
||||
"use_carousel_spawners": false,
|
||||
"gift_button_mode": 3
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"hzXcw6x2K7IiyjBC2tAar4Li2BBLyLO+v1L8AeoXGyo=": {
|
||||
"name": "hzXcw6x2K7IiyjBC2tAar4Li2BBLyLO+v1L8AeoXGyo=",
|
||||
"value": {
|
||||
"DefaultToQuickChat": false,
|
||||
"ShowRoomChatRefresh": true,
|
||||
"RevampedSpeechBubbles": false,
|
||||
"show_party_chat_in_room_chat_menu": false,
|
||||
"should_autofocus": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"w9MNMVCBq4BdjLvOo0JxK6eFU7Y9SJHWap1MNc0kfIw=": {
|
||||
"name": "w9MNMVCBq4BdjLvOo0JxK6eFU7Y9SJHWap1MNc0kfIw=",
|
||||
"value": {
|
||||
"block_and_mute_before_report": true,
|
||||
"block_and_mute_after_report": false,
|
||||
"mute_only_after_report": false,
|
||||
"block_only_after_report": false,
|
||||
"show_after_report_dialog": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"1ozMs7Rsz52kt8odUtvEqr7HFiyqIHH5jA6Yo1JgguY=": {
|
||||
"name": "1ozMs7Rsz52kt8odUtvEqr7HFiyqIHH5jA6Yo1JgguY=",
|
||||
"value": {
|
||||
"rrui_create_invention_use_rrui_menu": false,
|
||||
"rrui_create_invention_tool_deprecating_title": "",
|
||||
"rrui_create_invention_tool_depricating_description": "",
|
||||
"rrui_invention_details_use_rrui_menu": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"EruLhSARAlgHGn7MZBowYrStCD9NZ0q6Lm0JKofQal4=": {
|
||||
"name": "EruLhSARAlgHGn7MZBowYrStCD9NZ0q6Lm0JKofQal4=",
|
||||
"value": {
|
||||
"MaxRoomCount": -999,
|
||||
"Discovery.GenreBasedMatchmaking.FreshnessParameter": 5,
|
||||
"JoinOrCreateRoomCount": 10,
|
||||
"AdditionalJoinOnlyRoomCount": 15
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"6A3y9mbat5+nPpOJfCw+t7QD7F1+Ddcl89IKtktsWqI=": {
|
||||
"name": "6A3y9mbat5+nPpOJfCw+t7QD7F1+Ddcl89IKtktsWqI=",
|
||||
"value": {
|
||||
"use_item_rrp_upsell": true,
|
||||
"show_for_save": false,
|
||||
"show_for_publish": false,
|
||||
"RecRoomPlusSignUpBonusIds": [
|
||||
3
|
||||
]
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"c28poRnIhzrYvmxG7E9aPvW2t5azxdi3oUssGGtTYLM=": {
|
||||
"name": "c28poRnIhzrYvmxG7E9aPvW2t5azxdi3oUssGGtTYLM=",
|
||||
"value": {
|
||||
"direct_matchmaking": false,
|
||||
"CuratedList_PlayCategories": "Discovery.PageSource.PlayCategories"
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"aDdTxtyh0En2FjZj7mzo7EUDlr6uCMypxI7VscSeqKw=": {
|
||||
"name": "aDdTxtyh0En2FjZj7mzo7EUDlr6uCMypxI7VscSeqKw=",
|
||||
"value": {
|
||||
"PlayMenuCarouselOrdering_1": "",
|
||||
"PlayMenuCuratedCarouselsMap": "inkincroomhighlights=73682475;topearning=65380152;inkshowcase=52548973;quest=17859348;battle=17859349;horror=17859352;casualplay=17859347;roleplay=17859346;hangout=17859350;explore=17859351",
|
||||
"GroupName": "Default",
|
||||
"WatchHomeCarouselOrdering": "",
|
||||
"PlayMenuDedupeSources": "Hot,CarouselEndpoint",
|
||||
"EnforceOrderedRendering": true,
|
||||
"Lifecycle.Playmenu.CarouselEndpoint": "foryou:Recommended For You,rising:Rising Rooms,earnroomcurrency_algo:Earn Room Currency,roleplay_algoendpoint:Roleplay,casual_algoendpoint:Casual,horror_algoendpoint:Horror,battle_algoendpoint:Battle,explore_algoendpoint:Explore,quests_algoendpoint:Quests,hangout_algoendpoint:Hangout",
|
||||
"Lifecycle.Homemenu.CarouselEndpoint": "foryou:Recommended For You",
|
||||
"SegmentConfigMap": "",
|
||||
"EliteCreatorCarouselList": "",
|
||||
"AdvancedCreatorCarouselList": "",
|
||||
"MidCreatorCarouselList": "",
|
||||
"BeginnerCreatorCarouselList": "",
|
||||
"Lifecycle.WatchHome.SegmentConfigMap": "",
|
||||
"ShowHotRoomsForNewUsers": true,
|
||||
"Discovery.UseNewDiscoveryServerAPI": "True",
|
||||
"Lifecycle.PlayMenuCarouselHighlighting": "all=Favorites:highlight_scale_75",
|
||||
"HomeMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"PlayMenu.Carousels.MultiRowConfig": "{\"CarouselId\":\"Recommended For You\",\"MaxRows\":1,\"BackgroundColor\":\"#F3DDCE\",\"Enabled\":true}",
|
||||
"MobileHome.Discovery.Sections.v2": "[{\"title\":\"Friends\",\"isHorizontal\":true,\"source\":\"friends\"}, {\"title\":\"Featured\",\"source\":\"featuredRoom\",\"sourceMetadata\":\"PlayMenuRoomBanner\"}, {\"title\":\"Continue Playing\",\"isHorizontal\":true,\"source\":\"recent\"}, {\"title\":\"Rec Room Originals\",\"isHorizontal\":true,\"source\":\"hotList\",\"sourceMetadata\":\"rro\"}, {\"title\":\"Recommended For You\",\"isHorizontal\":true, \"hideIfNoContent\":true, \"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"foryou\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hot Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"HotList\\\",\\\"entityType\\\":1}\"}, {\"title\":\"New Rooms\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"new\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Battle\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"battle_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Horror\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"horror_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Casual\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"casual_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Roleplay\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"roleplay_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Hangout\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"hangout_algoendpoint\\\",\\\"entityType\\\":1}\"}, {\"title\":\"Explore\",\"isHorizontal\":true,\"source\":\"algorithmicList\",\"sourceMetadata\":\"{\\\"endpoint\\\":\\\"explore_algoendpoint\\\",\\\"entityType\\\":1}\"}]",
|
||||
"account_list_horizontal_card": false,
|
||||
"CuratedList_CommunityBoard": "Discovery.PageSource.CommunityBoard",
|
||||
"CuratedList_WatchHome": "Discovery.PageSource.WatchHome",
|
||||
"CuratedList_PlayHighlight": "Discovery.PageSource.PlayHighlight",
|
||||
"should_preload_sections": true,
|
||||
"CuratedList_MobileHome": "Discovery.PageSource.MobileHome",
|
||||
"algo_name": "RecentlyUpdated_meaningful_publish_boost",
|
||||
"algo_version": "",
|
||||
"number_of_entities_to_shuffle": "",
|
||||
"CuratedList_PlayCategories": "",
|
||||
"show_genre_card_in_room_details": false,
|
||||
"show_genre_cards_in_play_search": true,
|
||||
"room_categories_curated_list_name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
|
||||
"PageSourcesToReorder": "",
|
||||
"CuratedList_PlayMenuTabs": "Discovery.PageSource.PlayMenuTabs",
|
||||
"load_playmenu_tabs_from_server": true,
|
||||
"jumpToCarousel_PlayMenu": false,
|
||||
"can_save_for_later": true,
|
||||
"show_saved_for_later_tab": false,
|
||||
"loop_shimmer_animation": false,
|
||||
"hide_global_room_search": true,
|
||||
"consolidate_play_top_bar": true,
|
||||
"show_filters_with_embedded_search": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hCbzNX6Z7cCaB+hgMZSZWLKyWECKvsP58z/drNQiVkE=": {
|
||||
"name": "hCbzNX6Z7cCaB+hgMZSZWLKyWECKvsP58z/drNQiVkE=",
|
||||
"value": {
|
||||
"manipulate_enabled": true,
|
||||
"manipulate_collision_detection_enabled": true,
|
||||
"manipulate_touch_rot_degrees_per_inch": 120,
|
||||
"manipulate_toggle_enabled": true,
|
||||
"manipulate_quick_manipulate_enabled": false,
|
||||
"manipulate_disable_hand_collisions": false,
|
||||
"manipulate_cylindrical_placement_enabled": true,
|
||||
"manipulate_collision_outline_enabled": false,
|
||||
"manipulate_mp_surface_snap": true
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
],
|
||||
"undelegated_secondary_exposures": [
|
||||
{
|
||||
"gate": "1253665185",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
},
|
||||
{
|
||||
"gate": "2838138396",
|
||||
"gateValue": "false",
|
||||
"ruleID": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ENNwpFWf0+gU8kng8Bzxt/rlqY4NVuPFAKJV2hg2ZVU=": {
|
||||
"name": "ENNwpFWf0+gU8kng8Bzxt/rlqY4NVuPFAKJV2hg2ZVU=",
|
||||
"value": {
|
||||
"ShowNewAnnouncements": true,
|
||||
"ShowCloseButton": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
},
|
||||
"Q0U6YlMBoeNgCbnJ6WEwdQpwJgWRrvXeyq0JWodkUas=": {
|
||||
"name": "Q0U6YlMBoeNgCbnJ6WEwdQpwJgWRrvXeyq0JWodkUas=",
|
||||
"value": {
|
||||
"online_ranking_configuration_name": "2025q2_personalized_all_items",
|
||||
"use_online_ranking": true,
|
||||
"number_of_entities_to_shuffle": 18,
|
||||
"use_contextual_features": false
|
||||
},
|
||||
"rule_id": "default",
|
||||
"group": "default",
|
||||
"is_device_based": false,
|
||||
"explicit_parameters": [],
|
||||
"id_type": "userID",
|
||||
"secondary_exposures": [],
|
||||
"undelegated_secondary_exposures": []
|
||||
}
|
||||
},
|
||||
"sdkParams": {},
|
||||
"has_updates": true,
|
||||
"generator": "scrapi-nest",
|
||||
"time": 1788300073647,
|
||||
"company_lcut": 1788300073647,
|
||||
"evaluated_keys": {
|
||||
"userID": "205",
|
||||
"stableID": "69640e6ae1b54ae5b0ca8eeb4a8872ec6cf8fd88",
|
||||
"customIDs": {
|
||||
"RoomID": "12"
|
||||
}
|
||||
},
|
||||
"hash_used": "sha256",
|
||||
"derived_fields": {
|
||||
"ip": "1.2.3.4",
|
||||
"country": "US",
|
||||
"appVersion": "20250718.01",
|
||||
"app_version": "20250718.01",
|
||||
"deviceModel": "X570M Pro4 (To Be Filled By O.E.M.)",
|
||||
"device_model": "X570M Pro4 (To Be Filled By O.E.M.)",
|
||||
"browserName": "cURL",
|
||||
"browserVersion": "8.5.0",
|
||||
"osName": "Windows",
|
||||
"osVersion": "0.0.0",
|
||||
"browser_name": "cURL",
|
||||
"browser_version": "8.5.0",
|
||||
"os_name": "Windows",
|
||||
"os_version": "0.0.0"
|
||||
},
|
||||
"hashed_sdk_key_used": "3010233063",
|
||||
"can_record_session": true,
|
||||
"recording_blocked": false,
|
||||
"session_recording_rate": 1,
|
||||
"target_app_used": "unity-client"
|
||||
}
|
||||
Reference in New Issue
Block a user