Przeglądaj źródła

Document the emote ownership flags and how they were recovered

Records where an emote's ownership actually lives (the plug rule expression at
item-definition offset 720, not the collectible acquired expression), the
slot-vs-index distinction that account_flag_runs depends on, both number sets,
and how to regenerate them for a future build.
Millie 3 tygodni temu
rodzic
commit
9413682f70
1 zmienionych plików z 119 dodań i 0 usunięć
  1. 119 0
      Sunrise/docs/emote-unlocks.md

+ 119 - 0
Sunrise/docs/emote-unlocks.md

@@ -0,0 +1,119 @@
+# Emote ownership flags
+
+How this build decides an emote is owned, how the flags behind that were
+recovered, and the data itself. None of this is public: the Bungie manifest
+exposes only the failure message for these rules, never the expression behind
+it, so everything here was read out of the installed packages directly.
+
+## Where ownership actually lives
+
+**An emote's ownership is gated by its own item definition, not by a
+collectible.** Each emote item carries a plug rule -- the one whose failure
+message is *"You do not own this emote"* -- and that rule's unlock expression
+sits at **item-definition offset 720**. The enabled rule repeats the same
+expression at offset 800.
+
+The collectible table's acquired expression (`+112`) is a dead end for emotes:
+only 94 of this build's 307 emote items have a collectible row at all, so a
+collectible-driven pass cannot reach the other 213 no matter how it is written.
+
+Decoding offset 720 for every item in the individual-emote bucket (**41**)
+gives:
+
+| | count |
+| --- | ---: |
+| emote items in this build | 307 |
+| gated by an ownership flag | 291 |
+| distinct flag slots behind them | 288 |
+| carrying no expression at all | 16 |
+
+The 16 ungated ones are always owned -- that is why Yes, Nope and Cheer worked
+before any of this.
+
+## Slots are not indices
+
+This is the part that silently wastes a day.
+
+`state.unlocks.account_flag_runs` in `default_settings.json` does **not** hold
+flag slot numbers. It fills the account object's acquired-flag byte array, and
+the client addresses that array **by row number in the unlock flag mapping
+table**, not by slot. The two are unrelated number spaces.
+
+The mapping table for this bank:
+
+| | |
+| --- | --- |
+| investment root slot | 111 |
+| tag | `81319322` |
+| rows | 11923 |
+| row shape | `{ u32 unlock_hash; i16 destination_slot; u16 zero; }` |
+
+To set flag slot `s`, find the row whose `destination_slot == s`; that row's
+**number** is the index to write. Writing `s` itself sets an unrelated flag.
+
+Symptoms of getting this wrong, both observed here:
+
+- Writing slot numbers as indices does nothing visible, because the indices
+  that happen to be hit belong to unrelated slots.
+- Filling the whole bank "works" for emotes but also sets every entitlement
+  flag, which leaves the account unable to open the Director, the map, or
+  orbit. Do not blanket fill.
+
+Translating the 288 slots through the table yields **282 row indices**. The 6
+that do not resolve have no row in this table (they are reachable only through
+the family-5 override list, which is capped at 127 rows and so is not a route
+for a set this size).
+
+## The data
+
+Ownership flag **slots** (288) -- what the item definitions name:
+
+```
+229, 237-239, 264, 271, 2218, 2222, 2232-2238, 2240, 2242-2259, 3813-3820,
+3822-3836, 5200-5205, 5209-5220, 5228-5231, 5426-5427, 5429-5436, 6245-6264,
+6896-6902, 7350-7371, 7373-7374, 8159-8165, 8167-8182, 8999-9010, 9012-9013,
+9016-9021, 10408-10428, 10907-10932, 11431-11457, 11740-11770
+```
+
+Mapping-table **row indices** (282) -- what `account_flag_runs` must contain:
+
+```
+807, 811, 821-827, 829, 831-848, 2090-2097, 2099-2113, 3080-3085, 3089-3100,
+3107-3110, 3217-3218, 3220-3227, 3729-3748, 4195-4201, 4428-4449, 4451-4452,
+5057-5063, 5065-5080, 5676-5687, 5689-5690, 5693-5698, 6353-6373, 6734-6759,
+7148-7174, 7404-7434
+```
+
+161 of those indices were already set by the authored data, which is the 179
+emotes that were already owned (161 gated + 16 ungated + 2 sharing a flag).
+The change added the remaining 121.
+
+None of the 288 slots collides with any documented entitlement, platform, or
+pre-release slot. The Director, map and orbit were verified working afterwards.
+
+## Regenerating this
+
+The flag list is specific to this build; a content change invalidates it. To
+rebuild it, three temporary passes over the package data are needed:
+
+1. For every item with `bucketId == 41`, decode the unlock expression at
+   definition offset 720. A single `opcode 1` instruction carries the flag slot
+   as its operand. Expressions resolve as
+   `target = (offset of the pointer field) + (value stored there) + 16`, the
+   same self-relative form plus 16-byte block header that `find_array_at` uses.
+2. Read the mapping table at investment root slot 111 and build
+   `destination_slot -> row index`.
+3. Run-length-encode the union of the existing runs and the new indices.
+
+Watch the cache while doing this. `stale_format()` treats only
+`version < kCacheFormatVersion` as rebuildable, so a `build_data.bin` written
+by a *newer* format version is rejected outright rather than regenerated, which
+fails state initialisation and surfaces in the client as
+*"Verify integrity of game files"*. If the cache format version is changed and
+then reverted, delete `build_data.bin`.
+
+## Known gap
+
+These flags are recorded as a set. Which flag belongs to which *named* emote
+was never captured -- the extraction logged slots without their item hashes. It
+matters only if emotes ever need unlocking selectively rather than all at once.