mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 15:11:29 -07:00
oops, move skills
This commit is contained in:
@@ -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 |econ
|
||||
| `storefrontConfig` | object |
|
||||
| `roomKeyConfig` | object |
|
||||
| `roomCurrencyConfig` | object |
|
||||
| `shareBaseUrl` | string |
|
||||
|
||||
A `Dictionary<int,int>` 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` / `<activity-specific>` 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 <name> = <value>;` 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`.
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
name: weekly-challenge-config
|
||||
description: Read and author the `Config` rule tree in apps/econ/static/weekly-challenge.json — node types, scene-id predicates, and the shared-scene traps
|
||||
---
|
||||
|
||||
# The weekly-challenge `Config` rule tree
|
||||
|
||||
Reference for reading and writing the `Config` field of a challenge in
|
||||
`apps/econ/static/weekly-challenge.json` (served by `GET /api/challenge/v2/getCurrent`).
|
||||
|
||||
**The server never evaluates these rules.** The client reads the tree, watches its own
|
||||
gameplay, and posts the tree back to `/api/challenge/v2/updateProgress` with its verdict.
|
||||
So the tree is a _specification handed to the client_, and a malformed one fails silently —
|
||||
the challenge just never completes. Nothing server-side will tell you.
|
||||
|
||||
Everything here was read off one captured live rotation, not a spec. Meanings marked
|
||||
_(inferred)_ are read from how values line up with the strings the client renders; the rest
|
||||
are pinned by the data.
|
||||
|
||||
## `Config` is an escaped JSON string
|
||||
|
||||
Not a nested object. In the file it looks like:
|
||||
|
||||
```json
|
||||
"Config": "{\"ct\":0,\"ipc\":false,\"wc\":[...]}"
|
||||
```
|
||||
|
||||
Author the tree as an object and stringify it into the field — don't hand-escape:
|
||||
|
||||
```sh
|
||||
bun -e 'const t={ct:0,ipc:false,wc:[{ct:6,vs:[2]}]}; console.log(JSON.stringify(JSON.stringify(t)))'
|
||||
```
|
||||
|
||||
To read one back:
|
||||
|
||||
```sh
|
||||
bun -e 'const c=require("./apps/econ/static/weekly-challenge.json");
|
||||
for (const x of c.Challenges) console.log(x.ChallengeId, x.Description, "\n ", JSON.parse(x.Config))'
|
||||
```
|
||||
|
||||
## Node types
|
||||
|
||||
Each node carries a numeric type in `ct`. Two composite kinds appear:
|
||||
|
||||
- **Match** (`ct: 0`) — `wc` is a list of predicates that must _all_ hold for one game
|
||||
result (AND).
|
||||
- **Counter** (`ct: 1`) — `ctc` holds the child node to count, `t` is the target count.
|
||||
|
||||
Which slot a node uses (`wc` vs `ctc`) tells you what its children are; a node never has
|
||||
both. `ipc` is `false` on every composite node in the reference data — purpose unknown, but
|
||||
the client echoes it back, so keep emitting it.
|
||||
|
||||
## Predicate leaves
|
||||
|
||||
Leaves carry `vs`, a list of accepted values matched as OR.
|
||||
|
||||
| `ct` | Shape | Meaning |
|
||||
| ---- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `6` | `{"ct":6,"vs":[2]}` | _(inferred)_ The kind of event being matched — a finished game/session. Present in **every** leaf group and always `[2]`; nothing observed varying it, so treat it as required boilerplate. |
|
||||
| `7` | `{"ct":7,"vs":[{"l":"<guid>"}]}` | Scene allow-list: each `l` is a subroom's `UnitySceneId` (see `apps/rooms`). Matches if the game happened in any of them. |
|
||||
| `9` | `{"ct":9,"vs":[true],"v":"won"}` | A named session variable (`v`) equals one of `vs` — here, the player won. |
|
||||
|
||||
## The two idioms
|
||||
|
||||
Every challenge in the captured rotation is one of these.
|
||||
|
||||
```jsonc
|
||||
// "Complete ^TheRiseOfJumbotron quest" — one winning session in one scene
|
||||
{ "ct": 0, "ipc": false, "wc": [
|
||||
{ "ct": 6, "vs": [2] },
|
||||
{ "ct": 9, "vs": [true], "v": "won" },
|
||||
{ "ct": 7, "vs": [{ "l": "acc06e66-…" }] } // TheRiseofJumbotron / Home
|
||||
]}
|
||||
|
||||
// "Complete 5 Charades games" — count matching sessions to a target
|
||||
{ "ct": 1, "ipc": false, "t": 5, "ctc": [
|
||||
{ "ct": 0, "ipc": false, "wc": [
|
||||
{ "ct": 6, "vs": [2] },
|
||||
{ "ct": 7, "vs": [{ "l": "a673712c-…" }, { "l": "4078dfed-…" }] } // 3DCharades + Legacy3DCharades
|
||||
]}
|
||||
]}
|
||||
```
|
||||
|
||||
The quest challenges have **no `t`** (one qualifying session is the whole goal) and the
|
||||
counted ones have **no `won` predicate** (finishing counts, winning is irrelevant). A "one
|
||||
map only" challenge is the counted shape with a single-entry scene list.
|
||||
|
||||
## Scene ids, not room ids
|
||||
|
||||
`ct: 7` matches `UnitySceneId`, so one guid can name several rooms — a screens room, its VR
|
||||
twin, and the standalone base room all share a scene. The captured "Complete 10 games in
|
||||
^Paintball" lists six guids, which are the subrooms of _both_ `Paintball` and `PaintballVR`,
|
||||
each of which is also a standalone base room (`River`, `Clearcut`, …). One list covers every
|
||||
way in.
|
||||
|
||||
Resolve a guid against `SubRooms[].UnitySceneId` in `apps/rooms/static/ImportRooms.json`
|
||||
(same data as `apps/rooms/migrations/0002_import_rooms.sql`). Run from the repo root:
|
||||
|
||||
```sh
|
||||
cat > /tmp/scene.ts <<'EOF'
|
||||
// path is resolved against the cwd, so run this from the repo root
|
||||
const rooms = await Bun.file('apps/rooms/static/ImportRooms.json').json()
|
||||
const want = new Set(process.argv.slice(2))
|
||||
const byScene = new Map<string, string[]>()
|
||||
for (const r of rooms as any[])
|
||||
for (const s of r.SubRooms ?? [])
|
||||
byScene.set(s.UnitySceneId, [...(byScene.get(s.UnitySceneId) ?? []), `${r.Name}/${s.Name}`])
|
||||
for (const [id, names] of byScene) if (!want.size || want.has(id)) console.log(id, names.join(', '))
|
||||
EOF
|
||||
bun run /tmp/scene.ts 380d18b5-de9c-49f3-80f7-f4a95c1de161
|
||||
# → 380d18b5-… Paintball/Clearcut, PaintballVR/Clearcut, Clearcut/Home
|
||||
```
|
||||
|
||||
With no arguments it dumps every scene, which is how you go the other way — from a room name
|
||||
to the guid to put in `vs`.
|
||||
|
||||
### Shared scenes to watch for
|
||||
|
||||
These guids resolve to more than one room, so a challenge naming one also completes in the
|
||||
others. Most are a deliberate screens/VR/base-room trio, but two are genuine surprises:
|
||||
**`Soccer/Home` and `Stadium/Home` are the same scene**, so a soccer challenge also completes
|
||||
in the Stadium, and `Dodgeball` shares its scene with the plain `Gym`.
|
||||
|
||||
| Scene id | Rooms |
|
||||
| ----------- | ------------------------------------------------------------------ |
|
||||
| `6d5eea4b…` | Soccer/Home, **Stadium/Home** |
|
||||
| `3d474b26…` | Dodgeball/Home, **Gym/Home**, DodgeballVR/Home |
|
||||
| `ae929543…` | Bowling/Home, BowlingAlley/Home |
|
||||
| `f6f7256c…` | DiscGolfLake/Home, Lake/Home |
|
||||
| `d9378c9f…` | DiscGolfPropulsion/Home, PropulsionTestRange/Home |
|
||||
| `239e676c…` | LaserTag/Hangar, Hangar/Home |
|
||||
| `9d6456ce…` | LaserTag/CyberJunkCity, LaserTagCyberJunk/Home, CyberJunkCity/Home |
|
||||
| `e122fe98…` | Paintball/River, PaintballVR/River, River/Home |
|
||||
| `a785267d…` | Paintball/Homestead, PaintballVR/Homestead, Homestead/Home |
|
||||
| `ff4c6427…` | Paintball/Quarry, PaintballVR/Quarry, Quarry/Home |
|
||||
| `380d18b5…` | Paintball/Clearcut, PaintballVR/Clearcut, Clearcut/Home |
|
||||
| `58763055…` | Paintball/Spillway, PaintballVR/Spillway, Spillway/Home |
|
||||
| `65ddbb48…` | Paintball/Drive-in, PaintballVR/Drive-in, DriveIn/Home |
|
||||
|
||||
Regenerate this list with the script above and no arguments.
|
||||
|
||||
## Progress fields (`cc`, `c`) — client-side only
|
||||
|
||||
On `updateProgress` the client posts the same tree back with its own progress written into
|
||||
it: **`cc`** on the counter node is the current count (`…,"t":5,"cc":1`), and **`c`**
|
||||
(`"c":true`) marks a node it now considers satisfied.
|
||||
|
||||
Neither belongs in `weekly-challenge.json` — they are progress, not definition. The server
|
||||
echoes the posted `Config` back untouched and never persists it (`challenge_status` stores
|
||||
only the completion flag; see `apps/econ/src/challenge-db.ts`), so the running count lives
|
||||
only in the client. Don't add `cc`/`c` to an authored tree, and don't try to read progress
|
||||
out of one.
|
||||
|
||||
## Authoring a new challenge
|
||||
|
||||
1. Pick the idiom: one-shot (`ct: 0` root, add the `won` predicate if winning is required)
|
||||
or counted (`ct: 1` root with `t`).
|
||||
2. Resolve the scenes with the script above, and check the shared-scene table — decide
|
||||
whether the extra rooms it lets in are acceptable.
|
||||
3. Build the tree as an object, stringify it twice into `Config`.
|
||||
4. Give the entry a `ChallengeId` unique **within the rotation** (they aren't sequential),
|
||||
and write the real goal in `Description` — `Name` is an internal slug that is not
|
||||
authoritative (captured id `63` is named `Complete3SpillwayGames` but its `Config` and
|
||||
description are Clearcut).
|
||||
5. Leave `Complete: false`; `getCurrent` stamps it per caller.
|
||||
6. Bump `ChallengeMapId` if this is a new rotation — ids only need to be unique within one,
|
||||
and a new map id is what resets stored completions.
|
||||
7. Keep `ServerTime` inside `StartAt`…`EndAt`, or the client renders the rotation as expired.
|
||||
|
||||
Sanity check the file parses and every tree parses:
|
||||
|
||||
```sh
|
||||
bun -e 'const c=require("./apps/econ/static/weekly-challenge.json");
|
||||
c.Challenges.forEach(x => JSON.parse(x.Config)); console.log("ok", c.Challenges.length)'
|
||||
```
|
||||
|
||||
Then `bun turbo -F econ test` — `src/test/integration/api.test.ts` imports the file and
|
||||
asserts `getCurrent` against it.
|
||||
|
||||
## Related
|
||||
|
||||
- `apps/econ/README.md` — the rest of the weekly-challenge file (top level, `Gift`, progress)
|
||||
- `.agents/daily-objectives/SKILL.md` — the other objective system, on `GET api/config/v2`
|
||||
Reference in New Issue
Block a user