diff --git a/.agents/daily-objectives/SKILL.md b/.agents/daily-objectives/SKILL.md new file mode 100644 index 0000000..fcebc0f --- /dev/null +++ b/.agents/daily-objectives/SKILL.md @@ -0,0 +1,249 @@ +--- +name: daily-objectives +description: Guide for parsing/writing daily objectives in /api/config/v2 +--- + +# Daily objectives — config shape and the full type enum + +Reference for authoring `dailyObjectives` in `GET /api/config/v2`. Extracted from the 20230414 +client (`GameAssembly.dll` mtime 2026-07-23). See `SHAPES.md` for the method used. + +> **This table survives game upgrades.** Enum *member names and values* are not obfuscated — only +> type and method names re-roll per build. So the ids below stay valid across client versions unless +> Rec Room adds or removes members. The obfuscated names in this file (`PNLFAAAPEID`, +> `LCPOOJEAMJA`, …) are the only part that will go stale. + +## Where it lives + +`GET api/config/v2` (service `API`) → `JAGPNOHGHBG.DownloadConfigSettings`, deserialized as a bare +`LCPOOJEAMJA` via `SendWithRequiredResponseAsync` — response required, no envelope. + +Top-level keys, in declaration order (all accept three casings): + +| Wire name | Type | +| --- | --- | +| `levelProgressionMaps` | array of objects | +| **`dailyObjectives`** | **jagged array** — `FCAOHDFPEAP[][]` | +| `serverMaintenance` | object | +| `autoMicMutingConfig` | object | +| `storefrontConfig` | object | +| `roomKeyConfig` | object | +| `roomCurrencyConfig` | object | +| `shareBaseUrl` | string | + +A `Dictionary` declared first on the type carries `[IgnoreDataMember]` — client-only, never +on the wire. + +## `dailyObjectives` shape + +Array of arrays. Each leaf element (`FCAOHDFPEAP`, formatter `BMBOPLBKALL`) has exactly two members: + +| Wire name | Type | +| --- | --- | +| `type` | int — a value from the table below | +| `score` | int — the target / threshold | + +```json +{ + "dailyObjectives": [ + [ { "type": 1, "score": 1 }, + { "type": 6, "score": 5 }, + { "type": 31, "score": 3 } ], + [ { "type": 2, "score": 1 }, + { "type": 65, "score": 2 }, + { "type": 300, "score": 1 } ] + ] +} +``` + +**Unverified:** what the outer dimension indexes. The `updateobjective` DTO carries both `index` and +`group`, which lines up with `dailyObjectives[group][index]`, and `DailyObjective1/2/3` existing as +distinct types suggests three slots per set — but neither is confirmed against the consumer. Serve a +distinctive jagged array and watch the `group`/`index` pairs your endpoint receives. + +## Numbering scheme + +The ids are blocked, which tells you where new entries belong: + +| Range | Meaning | Count | +| --- | --- | --- | +| `-1` – `15` | meta / rollup / social | 16 | +| `20` – `26` | onboarding (OOBE, NUX) | 5 | +| `30` – `75` | general engagement | 45 | +| `100`+ | per-activity, one block each | 67 | + +Activity blocks follow a `Games` / `Wins` / `` pattern. Quest (`1000`) additionally +sub-blocks by scenario in steps of 10. + +**Careful with `10`–`15`.** `DailyObjective1/2/3`, `AllDailyObjectives`, `CompleteAnyDaily` and +`CompleteAnyWeekly` read as *rollup* types the reward system uses to track "you finished daily #1", +not as objective definitions themselves. Using them as leaf `type` values in `dailyObjectives` is +probably not what you want. (Inference from naming — not traced.) + +## Full enum — `PNLFAAAPEID`, 133 values + +| id | name | +| --- | --- | +| -1 | Default | +| 1 | FirstSessionOfDay | +| 2 | AddAFriend | +| 3 | PartyUp | +| 4 | AllOtherChallenges | +| 5 | LevelUp | +| 6 | CheerAPlayer | +| 7 | PointedAtPlayer | +| 8 | CheerARoom | +| 9 | SubscribeToPlayer | +| 10 | DailyObjective1 | +| 11 | DailyObjective2 | +| 12 | DailyObjective3 | +| 13 | AllDailyObjectives | +| 14 | CompleteAnyDaily | +| 15 | CompleteAnyWeekly | +| 20 | OOBE_GoToLockerRoom | +| 21 | OOBE_GoToActivity | +| 22 | OOBE_FinishActivity | +| 25 | NUX_PunchcardObjective | +| 26 | NUX_AllPunchcardObjectives | +| 30 | GoToRecCenter | +| 31 | FinishActivity | +| 32 | VisitACustomRoom | +| 33 | CreateACustomRoom | +| 35 | ScoreBasketInRecCenter | +| 36 | UploadPhotoToRecNet | +| 37 | UpdatePlayerBio | +| 38 | SaveOutfitSlot | +| 39 | PurchaseClothingItem | +| 40 | PurchaseNonClothingItem | +| 41 | DrinkWater | +| 42 | ColorOnWhiteboard | +| 43 | SetBasketballSkin | +| 44 | ThrowBasketball | +| 45 | PlaceInventionInDorm | +| 46 | ChangeDormRoomSkin | +| 47 | ToggleOwnedClothes | +| 48 | EquipHat | +| 49 | LoadOutfit | +| 50 | SaveNewOutfitSlot | +| 51 | SpawnCamera | +| 52 | TakeSelfie | +| 53 | PrintSelfie | +| 54 | TakePictureOfPlayer | +| 55 | PrintPictureOfPlayer | +| 56 | PublishSelfieWithPlayer | +| 57 | SpawnFoodWithOtherPlayers | +| 58 | EmoteInRecCenter | +| 59 | SendRoomChatInRecCenter | +| 60 | UseFrendotron | +| 61 | GoToDormRoom | +| 62 | VisitSpecificRoom | +| 63 | VisitPublicRRO | +| 64 | VisitPublicRoomBySource | +| 65 | FavoriteARoom | +| 66 | TakePhotoWithFilter | +| 67 | OpenYourPlayerProfile | +| 68 | OpenOnlineStatusModal | +| 69 | ChangeProfilePicture | +| 70 | ChangePlayerDisplayName | +| 71 | ChangePlayerDescriptionText | +| 72 | OpenPlayerPronounsModal | +| 73 | OpenOtherPlayersProfile | +| 74 | VisitPlayersPortfolio | +| 75 | FavoriteAFriend | +| 100 | CharadesGames | +| 101 | CharadesWinsPerformer | +| 102 | CharadesWinsGuesser | +| 200 | DiscGolfWins | +| 201 | DiscGolfGames | +| 202 | DiscGolfHolesUnderPar | +| 300 | DodgeballWins | +| 301 | DodgeballGames | +| 302 | DodgeballHits | +| 400 | PaddleballGames | +| 401 | PaddleballWins | +| 402 | PaddleballScores | +| 500 | PaintballAnyModeGames | +| 501 | PaintballAnyModeWins | +| 502 | PaintballAnyModeHits | +| 600 | PaintballCTFWins | +| 601 | PaintballCTFGames | +| 602 | PaintballCTFHits | +| 603 | PaintballFlagCaptures | +| 700 | PaintballTeamBattleWins | +| 701 | PaintballTeamBattleGames | +| 702 | PaintballTeamBattleHits | +| 710 | PaintballFreeForAllWins | +| 711 | PaintballFreeForAllGames | +| 712 | PaintballFreeForAllHits | +| 800 | SoccerWins | +| 801 | SoccerGames | +| 802 | SoccerGoals | +| 900 | BowlingGames | +| 901 | BowlingWins | +| 902 | BowlingStrike | +| 1000 | QuestGames | +| 1001 | QuestWins | +| 1002 | QuestPlayerRevives | +| 1003 | QuestEnemyKills | +| 1010 | QuestGames_Goblin1 | +| 1011 | QuestWins_Goblin1 | +| 1012 | QuestPlayerRevives_Goblin1 | +| 1013 | QuestEnemyKills_Goblin1 | +| 1020 | QuestGames_Goblin2 | +| 1021 | QuestWins_Goblin2 | +| 1022 | QuestPlayerRevives_Goblin2 | +| 1023 | QuestEnemyKills_Goblin2 | +| 1030 | QuestGames_Scifi1 | +| 1031 | QuestWins_Scifi1 | +| 1032 | QuestPlayerRevives_Scifi1 | +| 1033 | QuestEnemyKills_Scifi1 | +| 1040 | QuestGames_Pirate1 | +| 1041 | QuestWins_Pirate1 | +| 1042 | QuestPlayerRevives_Pirate1 | +| 1043 | QuestEnemyKills_Pirate1 | +| 1050 | QuestGames_Dracula1 | +| 1051 | QuestWins_Dracula1 | +| 1052 | QuestPlayerRevives_Dracula1 | +| 1053 | QuestEnemyKills_Dracula1 | +| 2000 | ArenaGames | +| 2001 | ArenaWins | +| 2002 | ArenaPlayerRevives | +| 2003 | ArenaHeroTags | +| 2004 | ArenaBotTags | +| 3000 | RecRoyaleGames | +| 3001 | RecRoyaleWins | +| 3002 | RecRoyaleTags | +| 4000 | StuntRunnerGames | +| 4001 | StuntRunnerWins | +| 5000 | RecRallyGames | +| 5001 | RecRallyWins | + +## Machine-readable + +```json +{"Default":-1,"FirstSessionOfDay":1,"AddAFriend":2,"PartyUp":3,"AllOtherChallenges":4,"LevelUp":5,"CheerAPlayer":6,"PointedAtPlayer":7,"CheerARoom":8,"SubscribeToPlayer":9,"DailyObjective1":10,"DailyObjective2":11,"DailyObjective3":12,"AllDailyObjectives":13,"CompleteAnyDaily":14,"CompleteAnyWeekly":15,"OOBE_GoToLockerRoom":20,"OOBE_GoToActivity":21,"OOBE_FinishActivity":22,"NUX_PunchcardObjective":25,"NUX_AllPunchcardObjectives":26,"GoToRecCenter":30,"FinishActivity":31,"VisitACustomRoom":32,"CreateACustomRoom":33,"ScoreBasketInRecCenter":35,"UploadPhotoToRecNet":36,"UpdatePlayerBio":37,"SaveOutfitSlot":38,"PurchaseClothingItem":39,"PurchaseNonClothingItem":40,"DrinkWater":41,"ColorOnWhiteboard":42,"SetBasketballSkin":43,"ThrowBasketball":44,"PlaceInventionInDorm":45,"ChangeDormRoomSkin":46,"ToggleOwnedClothes":47,"EquipHat":48,"LoadOutfit":49,"SaveNewOutfitSlot":50,"SpawnCamera":51,"TakeSelfie":52,"PrintSelfie":53,"TakePictureOfPlayer":54,"PrintPictureOfPlayer":55,"PublishSelfieWithPlayer":56,"SpawnFoodWithOtherPlayers":57,"EmoteInRecCenter":58,"SendRoomChatInRecCenter":59,"UseFrendotron":60,"GoToDormRoom":61,"VisitSpecificRoom":62,"VisitPublicRRO":63,"VisitPublicRoomBySource":64,"FavoriteARoom":65,"TakePhotoWithFilter":66,"OpenYourPlayerProfile":67,"OpenOnlineStatusModal":68,"ChangeProfilePicture":69,"ChangePlayerDisplayName":70,"ChangePlayerDescriptionText":71,"OpenPlayerPronounsModal":72,"OpenOtherPlayersProfile":73,"VisitPlayersPortfolio":74,"FavoriteAFriend":75,"CharadesGames":100,"CharadesWinsPerformer":101,"CharadesWinsGuesser":102,"DiscGolfWins":200,"DiscGolfGames":201,"DiscGolfHolesUnderPar":202,"DodgeballWins":300,"DodgeballGames":301,"DodgeballHits":302,"PaddleballGames":400,"PaddleballWins":401,"PaddleballScores":402,"PaintballAnyModeGames":500,"PaintballAnyModeWins":501,"PaintballAnyModeHits":502,"PaintballCTFWins":600,"PaintballCTFGames":601,"PaintballCTFHits":602,"PaintballFlagCaptures":603,"PaintballTeamBattleWins":700,"PaintballTeamBattleGames":701,"PaintballTeamBattleHits":702,"PaintballFreeForAllWins":710,"PaintballFreeForAllGames":711,"PaintballFreeForAllHits":712,"SoccerWins":800,"SoccerGames":801,"SoccerGoals":802,"BowlingGames":900,"BowlingWins":901,"BowlingStrike":902,"QuestGames":1000,"QuestWins":1001,"QuestPlayerRevives":1002,"QuestEnemyKills":1003,"QuestGames_Goblin1":1010,"QuestWins_Goblin1":1011,"QuestPlayerRevives_Goblin1":1012,"QuestEnemyKills_Goblin1":1013,"QuestGames_Goblin2":1020,"QuestWins_Goblin2":1021,"QuestPlayerRevives_Goblin2":1022,"QuestEnemyKills_Goblin2":1023,"QuestGames_Scifi1":1030,"QuestWins_Scifi1":1031,"QuestPlayerRevives_Scifi1":1032,"QuestEnemyKills_Scifi1":1033,"QuestGames_Pirate1":1040,"QuestWins_Pirate1":1041,"QuestPlayerRevives_Pirate1":1042,"QuestEnemyKills_Pirate1":1043,"QuestGames_Dracula1":1050,"QuestWins_Dracula1":1051,"QuestPlayerRevives_Dracula1":1052,"QuestEnemyKills_Dracula1":1053,"ArenaGames":2000,"ArenaWins":2001,"ArenaPlayerRevives":2002,"ArenaHeroTags":2003,"ArenaBotTags":2004,"RecRoyaleGames":3000,"RecRoyaleWins":3001,"RecRoyaleTags":3002,"StuntRunnerGames":4000,"StuntRunnerWins":4001,"RecRallyGames":5000,"RecRallyWins":5001} +``` + +## Related endpoints + +| Endpoint | Purpose | +| --- | --- | +| `GET api/config/v2` | serves `dailyObjectives` (this file) | +| `GET api/objectives/v1/myprogress` | player's current progress | +| `POST api/objectives/v1/updateobjective` | one objective update — `{index, group, progress, visualProgress, isCompleted, hasClaimedReward}` → `{group, isCompleted, clearedAt}` | +| `POST api/objectives/v1/completegroup` | group completion | +| `POST api/objectives/v1/cleargroup` | group reset | + +The objectives endpoints are on the **Econ** service (`econ.*`); config is on **API** (`api.*`). + +## How this was extracted + +```sh +# in the il2cpp scratchpad, with Il2CppDumper output in ./out/ +grep -n "enum PNLFAAAPEID" out/dump.cs # find the block +# then parse `public const PNLFAAAPEID = ;` lines until the closing brace +``` + +The `dailyObjectives` wire name came from the Utf8Json formatter, not the property name — see +`SHAPES.md` §1. Formatter `.ctor` RVAs for this build: `LCPOOJEAMJA` → `0x3512E10`, +`FCAOHDFPEAP` → `0x34EE5F0`.