`UnitTokens::resolve` had no arm for `partypetN` or `raidpetN`. `"partypet1"` fell into the `party` arm and failed to parse `pet1`, and `"raidpet1"` did the same in the `raid` arm. As a result, the stock TARGETPARTYPET1-4 bindings, the pet half of the TARGETPARTYMEMBER toggle, `TargetUnit`, `AssistUnit` and `SpellTargetUnit` all did nothing on a party or raid pet.
The reference's resolver `0x515970` tests `partypet` before `party` (`0x515a9a`) and `raidpet` before `raid` (`0x515b1c`):
- `partypetN` (`0x491930` → `0x4e81d0`) takes the Nth party slot's member. It names that member's `CHARM`, else its `SUMMON`, while the member is held. Otherwise it names the pet guid on the party record, behind the member's online bit (`0x4e8221`-`0x4e8230`).
- `raidpetN` (`0x491960`) does the same for the Nth raid row, bounded by the raid count, with no online test.
Changes:
- The two arms resolve through the existing `GroupState::party_pet_guid` and `raid_pet_guid`, then the object manager.
- The reference's `partypetN` has no bound. Here an index outside 1-4 names nobody, as it does for `partyN`.
- The four group arms now share one digit parse, `slot_number`, which is digits-only as the reference's is. So `"party+1"` no longer resolves.
- `reach_tokens` now covers the pet tokens, so `CheckInteractDistance`, `CanInspect` and `SpellCanTargetUnit` measure them.
- The token tables `ui_aura` kept privately now live in `ui_party` beside `PARTY_TOKENS` and `RAID_TOKENS`.
Tests:
- `ui_unit.rs`'s `group_pet_tokens` covers the order against `party`/`raid`, case folding, the bounds, charm-else-summon, the record leg and its online bit, raid rows and the reach feed.
- `target/script.rs` adds `target_unit_selects_a_party_pet`.
This is the resolver half of #448. The per-token unit snapshot that `UnitExists`, `UnitName` and `UnitHealth` read does not push the pet tokens yet, so the party pet frames stay hidden until that half lands.
Part of #448🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>