fix(ui): top-left HUD portraits for party members, mercenaries, and summoned minions (1.13c Hireables.cpp parity) #556

Closed
opened 2026-10-02 09:27:06 +00:00 by troytt · 1 comment
Owner

Bug Report: Top-Left HUD Portraits (Party, Mercenary & Summoned Minions)

Symptoms (play.html)

  1. Phantom Player #1 Portrait in Solo / Unpartied Games: Upon entering a game solo, a top-left portrait box labeled Player #1 appears at (8, 72) even when unpartied (partyId === 0xffff) or when 0x7F (isPlayer === 0) / 0x8B / 0x5B arrives for selfId.
  2. Garbled Double-Rendered Portrait Labels: HudManager.render calls both ctx.fillText(...) (browser sans-serif) and this.font.drawText(...) (Diablo II bitmap font6) at the same coordinates, causing overlapping double text.
  3. Missing Authentic 1.13c DC6 Portrait Sprites (data/global/ui/Hireables/*.dc6): Top-left portraits draw plain black rectangles instead of the 29 authentic DC6 icons (7 player classes, 4 hirelings, 18 summoned minions from PetType.txt).
  4. Missing Summoned Minion (0x7A SummonAction / RosterPets) Tracking & Top-Left HUD Display: decode0x7ASummonAction (0x6fb5c720) currently emits type: "Merc", kind: "assign", overwriting world.merc instead of maintaining a RosterPets map (0x6fad1840 / 0x6fad18c0) and rendering grouped/sub-typed summon portraits (icontype 1..3) with count badges.

1.13c Binary Ground Truth

  • D2Client.dll Hireables.cpp (0x6fb0ac70..0x6fb0cb50): Top-left portraits (46x41 DC6 frames + 46x5 HP bars), PetType.txt icon resolution (baseicon + mclass1..3), icontype=2 count badge (0x6fb0add0), and allied party filtering (0x6fb0ba90).
  • D2Client.dll RosterPets.cpp (0x6fad1800..0x6fad1e50) & packet handlers 0x7A SummonAction (0x6fb5c720), 0x7F AllyPartyInfo (0x6fb5d820), 0x81 AssignMerc (0x6fb5eba0).

Screenshot Evidence

bug-portrait-hud.png

## Bug Report: Top-Left HUD Portraits (Party, Mercenary & Summoned Minions) ### Symptoms (`play.html`) 1. **Phantom `Player #1` Portrait in Solo / Unpartied Games**: Upon entering a game solo, a top-left portrait box labeled `Player #1` appears at `(8, 72)` even when unpartied (`partyId === 0xffff`) or when `0x7F` (`isPlayer === 0`) / `0x8B` / `0x5B` arrives for `selfId`. 2. **Garbled Double-Rendered Portrait Labels**: `HudManager.render` calls both `ctx.fillText(...)` (browser sans-serif) and `this.font.drawText(...)` (Diablo II bitmap `font6`) at the same coordinates, causing overlapping double text. 3. **Missing Authentic 1.13c DC6 Portrait Sprites (`data/global/ui/Hireables/*.dc6`)**: Top-left portraits draw plain black rectangles instead of the 29 authentic DC6 icons (7 player classes, 4 hirelings, 18 summoned minions from `PetType.txt`). 4. **Missing Summoned Minion (`0x7A SummonAction` / `RosterPets`) Tracking & Top-Left HUD Display**: `decode0x7ASummonAction` (`0x6fb5c720`) currently emits `type: "Merc", kind: "assign"`, overwriting `world.merc` instead of maintaining a `RosterPets` map (`0x6fad1840` / `0x6fad18c0`) and rendering grouped/sub-typed summon portraits (`icontype 1..3`) with count badges. ### 1.13c Binary Ground Truth - `D2Client.dll` `Hireables.cpp` (`0x6fb0ac70..0x6fb0cb50`): Top-left portraits (`46x41` DC6 frames + `46x5` HP bars), `PetType.txt` icon resolution (`baseicon` + `mclass1..3`), `icontype=2` count badge (`0x6fb0add0`), and allied party filtering (`0x6fb0ba90`). - `D2Client.dll` `RosterPets.cpp` (`0x6fad1800..0x6fad1e50`) & packet handlers `0x7A SummonAction` (`0x6fb5c720`), `0x7F AllyPartyInfo` (`0x6fb5d820`), `0x81 AssignMerc` (`0x6fb5eba0`). ### Screenshot Evidence ![bug-portrait-hud.png](https://git.projectdiablo2.cn/attachments/5bb4e5df-7990-49dd-9020-39aeff362318)
Author
Owner

Resolution & 1.13c Parity Verification Report

Root Cause Resolutions

  1. Phantom Player #1 Portrait Eliminated:

    • Fixed decode0x7FPartyMemberState (0x6fb5d820) to preserve isPlayer: isPlayer !== 0. Packets with isPlayer === 0 update pet / hireling life in RosterPets (0x6fad1950) rather than inserting nameless phantom players into ClientWorld._party.
    • Updated HudModel.syncPartyRoster to exclude world.selfId from hud.partyMembers and worldPanels.partyMembers.
    • Updated HudManager.render to only render top-left HUD portraits for allied party members (partyState === "in_party" / partyState === 1 / partyId !== 0xffff), keeping unpartied players scoped to the P Party Screen.
  2. Garbled Overlapping Text Fixed:

    • Removed duplicate ctx.fillText(...) call before this.font.drawText(...) in HudManager.render. Labels now render cleanly once using authentic Diablo II font6 bitmap typography.
  3. Packed 29 Authentic DC6 Portrait Frames (data/global/ui/Hireables/*.dc6):

    • Extracted and packed into public/ui/portraits-atlas.png (288x210) with metadata in manifest.json and src/common/items/baked-ui-meta.ts:
      • 7 Player Classes (AmazonIcon, SorceressIcon, NecromancerIcon, PaladinIcon, BarbarianIcon, DruidIcon, AssassinIcon).
      • 4 Hirelings (rogueicon, act2hireableicon, act3hireableicon, barbhirable_icon).
      • 18 Summoned Minions (PetType.txt rows 2..6, 10..16).
  4. 1.13c Summoned Minions (RosterPets) Tracking & Top-Left HUD Grouping:

    • Fixed decode0x7ASummonAction (0x6fb5c720) with isSummonAction: true, summonAction: action, petType.
    • Added world.pets (Map<number, RosterPetState>) to track summoned pets without overwriting world.merc.
    • HudManager.render groups icontype=2 multi-summons (Skeletons, Wolves, Ravens) with top-left count badges (0x6fb0add0), selects sub-type icons for icontype=3 summons (Golems, Spirits, Vines), and renders life bars per PetType.txt rules (0x6fb0b5e6).

Verification

  • npm run typecheck: 0 errors across all 7 tsconfig projects.
  • tests/client/hud-portrait-roster-pets.test.ts: 7/7 passed.
  • tests/client/hud-server-authority.test.ts: 14/14 passed.
  • tests/e2e-d2gs-online/ (Tiers 1..5): all 204 tests passed.
  • tests/skills/ (Necromancer, Paladin, Druid): all tests passed.

Merged to main via commit 9e749a2.

## Resolution & 1.13c Parity Verification Report ### Root Cause Resolutions 1. **Phantom `Player #1` Portrait Eliminated**: - Fixed `decode0x7FPartyMemberState` (`0x6fb5d820`) to preserve `isPlayer: isPlayer !== 0`. Packets with `isPlayer === 0` update pet / hireling life in `RosterPets` (`0x6fad1950`) rather than inserting nameless phantom players into `ClientWorld._party`. - Updated `HudModel.syncPartyRoster` to exclude `world.selfId` from `hud.partyMembers` and `worldPanels.partyMembers`. - Updated `HudManager.render` to only render top-left HUD portraits for allied party members (`partyState === "in_party"` / `partyState === 1` / `partyId !== 0xffff`), keeping unpartied players scoped to the `P` Party Screen. 2. **Garbled Overlapping Text Fixed**: - Removed duplicate `ctx.fillText(...)` call before `this.font.drawText(...)` in `HudManager.render`. Labels now render cleanly once using authentic Diablo II `font6` bitmap typography. 3. **Packed 29 Authentic DC6 Portrait Frames** (`data/global/ui/Hireables/*.dc6`): - Extracted and packed into `public/ui/portraits-atlas.png` (`288x210`) with metadata in `manifest.json` and `src/common/items/baked-ui-meta.ts`: - 7 Player Classes (`AmazonIcon`, `SorceressIcon`, `NecromancerIcon`, `PaladinIcon`, `BarbarianIcon`, `DruidIcon`, `AssassinIcon`). - 4 Hirelings (`rogueicon`, `act2hireableicon`, `act3hireableicon`, `barbhirable_icon`). - 18 Summoned Minions (`PetType.txt` rows 2..6, 10..16). 4. **1.13c Summoned Minions (`RosterPets`) Tracking & Top-Left HUD Grouping**: - Fixed `decode0x7ASummonAction` (`0x6fb5c720`) with `isSummonAction: true, summonAction: action, petType`. - Added `world.pets` (`Map<number, RosterPetState>`) to track summoned pets without overwriting `world.merc`. - `HudManager.render` groups `icontype=2` multi-summons (Skeletons, Wolves, Ravens) with top-left count badges (`0x6fb0add0`), selects sub-type icons for `icontype=3` summons (Golems, Spirits, Vines), and renders life bars per `PetType.txt` rules (`0x6fb0b5e6`). ### Verification - `npm run typecheck`: 0 errors across all 7 tsconfig projects. - `tests/client/hud-portrait-roster-pets.test.ts`: 7/7 passed. - `tests/client/hud-server-authority.test.ts`: 14/14 passed. - `tests/e2e-d2gs-online/` (Tiers 1..5): all 204 tests passed. - `tests/skills/` (Necromancer, Paladin, Druid): all tests passed. Merged to `main` via commit `9e749a2`.
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#556
No description provided.