docs: Campaign CT plan — character-panel parity (Titles page, header identity, resize/scrollbar, row alignment)
Owner-reported 2026-08-24 batch, recon-verified against gmCharacterTitleUI @0x0049A610 / gmStatManagementUI::PostInit @0x004EFD90 / ACE's CharacterTitle-UpdateTitle-TitleSet wire trio. Retires AP-109 when CT3/CT4 land. Fable plans, Sonnet implements, Opus dual-lens reviews; no push until the owner directs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
aa94ddebe3
commit
1cd4dfc92a
1 changed files with 139 additions and 0 deletions
139
docs/plans/2026-08-24-character-panel-parity-campaign.md
Normal file
139
docs/plans/2026-08-24-character-panel-parity-campaign.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# Campaign CT — Character-panel retail parity (header identity, Titles page, resize/scrollbar, row alignment)
|
||||
|
||||
**Status:** PLANNED 2026-08-24 (owner gate report after the CA5/chat rounds).
|
||||
**Execution model:** Fable plans and coordinates; Sonnet implements each
|
||||
slice; Opus runs the dual-lens review (retail-faithful + architectural)
|
||||
per slice, then a fix round. No pushes to gitea until the owner says so.
|
||||
**Register:** this campaign retires AP-109 (inert Titles page) when CT3+CT4
|
||||
land; every deviation a slice introduces adds its row in the same commit.
|
||||
|
||||
## Owner report (2026-08-24, screenshots on file)
|
||||
|
||||
1. Attribute/skill row **icons misaligned** vs retail.
|
||||
2. Retail keeps a **margin between the value column and the border** —
|
||||
the gutter reserved for the list scrollbar that appears when the
|
||||
window is resized shorter. We author no such margin and never show
|
||||
the scrollbar on Attributes.
|
||||
3. The character window is **resizable in Y down to an authored
|
||||
minimum** in retail; ours is not.
|
||||
4. Header identity block: retail shows the name, then
|
||||
**"<Gender> <Heritage> <DisplayTitle>"** (e.g. "Female Aluvian War
|
||||
Mage"), then **"Non-Player Killer" / "Player Killer" /
|
||||
"Player Killer Lite"** in **pure white** — on Attributes AND Skills.
|
||||
We show gender+heritage only, no PK line contract, color off.
|
||||
5. **Level number color** slightly off vs retail.
|
||||
6. **Titles tab is inert** (AP-109): retail lists all earned titles
|
||||
(sorted), shows the current display title, and lets the player set
|
||||
one ("Set as Display Title"); scrollbar with many titles; the
|
||||
header identity line updates live when the display title changes.
|
||||
7. **All windows share retail's authored minimum-size behavior** —
|
||||
resize clamps to the authored constraints everywhere.
|
||||
|
||||
## Retail recon (verified 2026-08-24, this session)
|
||||
|
||||
### Titles page — `gmCharacterTitleUI`
|
||||
- `PostInit @0x0049A610` binds: display-title text `0x1000052F`,
|
||||
"Set as Display Title" button `0x10000535`, title ListBox
|
||||
`0x10000532`. Registers notice handlers for the title-table /
|
||||
add-title / set-display-title notices.
|
||||
- Rows carry the title id in attribute `0x1000008E`;
|
||||
`AddTitleToList @0x0049A840` resolves the display string via
|
||||
`CharacterTitleTable::GetCharacterTitleFromID` (DAT title-string
|
||||
table — CT2 locates the DID) and inserts SORTED
|
||||
(`FindSortedInsertPosition @0x0049A760`).
|
||||
- `UpdateButtons @0x0049A500`: the display button GHOSTS (state 0xd)
|
||||
when the selected row's title id == `mDisplayTitle`; Normal (1)
|
||||
otherwise. Selection change (msg 4/0x43) re-runs it.
|
||||
- Clicking `0x10000535` sends
|
||||
`CM_Social::Event_SetDisplayCharacterTitle(titleId)`
|
||||
(`ListenToElementMessage @0x0049A6D0`).
|
||||
- `gmStatManagementUI::RecvNotice_SetDisplayCharacterTitle @0x004EFD50`
|
||||
→ the stat panel refreshes its header when the display title changes.
|
||||
|
||||
### Wire (ACE cross-checked)
|
||||
- Inbound `CharacterTitle` event `0x0029` (already in our
|
||||
`GameEventType`): `u32 =1, u32 displayTitleId, u32 count,
|
||||
count × u32 titleId` (`GameEventCharacterTitle.cs`).
|
||||
- Inbound `UpdateTitle` event `0x002B`: `u32 titleId,
|
||||
u32 setAsDisplay` (`GameEventUpdateTitle.cs`).
|
||||
- Outbound `TitleSet` GameAction (`GameActionSetTitle.cs`):
|
||||
`u32 titleId`. Retail sender: `CM_Social::Event_SetDisplayCharacterTitle`.
|
||||
|
||||
### Header identity — `gmStatManagementUI::PostInit @0x004EFD90`
|
||||
Binds name `0x10000231`, heritage line `0x10000232`, PK line
|
||||
`0x10000233`, level `0x1000023B`, total XP `0x10000235`, XP-to-level
|
||||
`0x10000238` + meter `0x10000236`, luminance pair `0x100005C5/C6`, list
|
||||
box `0x1000023D`. The refresh (vtable slot, near
|
||||
`UpdateExperience @0x004F0A70`) composes the heritage line WITH the
|
||||
display title; the PK strings are exactly "Player Killer" /
|
||||
"Player Killer Lite" / "Non-Player Killer" (IsPK / IsPKLite —
|
||||
cross-anchor `CharExamineUI::SetAppraiseInfo @0x004B45F0`). CT5 reads
|
||||
the composing function verbatim before writing a line of C#.
|
||||
|
||||
### Already in-tree
|
||||
- Tab/page ids wired (`TabTitlesId 0x10000538`, `TitlesPageId
|
||||
0x10000539`); pages currently show retail-authored closed visuals.
|
||||
- Header labels partially bound (`StatHeaderLine` + `PkStatus` seams
|
||||
exist in `CharacterStatController.Bind` — content contract wrong).
|
||||
- `GameEventType.CharacterTitle/UpdateTitle` enum entries exist; no
|
||||
parser, no state owner, no outbound builder.
|
||||
- The character window registers with `DatConstraintSource` — authored
|
||||
min/max plumbing exists in `RetailWindowFrame`; Y-resize for this
|
||||
window and the list-scrollbar contract do not.
|
||||
|
||||
## Slices
|
||||
|
||||
**CT1 — DAT ground truth + pins.** Live-DAT probe of layout
|
||||
`0x2100002E`: attribute/skill row templates (icon x/y vs our hand-built
|
||||
rows), the value-column right margin, header element fonts/colors
|
||||
(level `0x1000023B` color — item 5), Titles-page elements
|
||||
(`0x1000052F/32/35` geometry, row template, scrollbar), window
|
||||
min/max constraints. Output: research doc + InstalledDat pins (the
|
||||
tooltip/scrollbar-pin pattern). No production changes.
|
||||
|
||||
**CT2 — Runtime title ownership + wire.** Parse `0x0029`/`0x002B`;
|
||||
locate the DAT title-string table `GetCharacterTitleFromID` reads and
|
||||
port the lookup; `RuntimeCharacterState` owns the title set + display
|
||||
title (J4.3 owner; clears at generation reset); outbound `TitleSet`
|
||||
builder behind a typed Runtime command; ordered change events for UI
|
||||
and headless bots (#368 contract: hosts observe the same owner).
|
||||
Conformance tests against ACE's writer shapes.
|
||||
|
||||
**CT3 — Titles page UI.** Bind the authored page through the standard
|
||||
GUI classes (`UiTemplateListBox`/`UiScrollbar`/`UiButton` — zero
|
||||
bespoke widgets): sorted rows via the ported title-table lookup,
|
||||
selection, ghost-when-current logic (state 0xd contract), display-title
|
||||
text, Set-as-Display round trip, scrollbar. Retires half of AP-109.
|
||||
|
||||
**CT4 — Header identity block.** Retail composition: name; "<Gender>
|
||||
<Heritage> <DisplayTitle>"; PK status line — authored fonts/colors
|
||||
(pure white per probe), live refresh on display-title change and PK
|
||||
status, identical on Attributes AND Skills pages. Level color from the
|
||||
authored element. Retires the rest of AP-109's UI half.
|
||||
|
||||
**CT5 — Row alignment + value gutter.** Reconcile our hand-built
|
||||
attribute/skill rows with the authored row templates from CT1: icon
|
||||
placement, name/value columns, the authored right margin that reserves
|
||||
the scrollbar gutter.
|
||||
|
||||
**CT6 — Resize + scrollbar contract.** Character window Y-resizable to
|
||||
the authored minimum; the stat list shows its scrollbar when the
|
||||
resized viewport overflows (the full-track/disabled behavior from the
|
||||
2026-08-24 scrollbar work applies as-is); authored min/max constraint
|
||||
enforcement verified as the STANDARD path for every registered window
|
||||
(one shared mechanism in `RetailWindowFrame`/`RetailWindowManager`,
|
||||
no per-window special cases).
|
||||
|
||||
**CT7 — Connected gate.** Test script
|
||||
(`docs/research/2026-08-24-campaign-ct-test-script.md`), owner drive:
|
||||
titles round trip against ACE (earn/set/display), header lines vs
|
||||
retail side-by-side, resize behavior, row alignment screenshots.
|
||||
|
||||
## Review protocol
|
||||
|
||||
Per slice: Sonnet implements → Opus dual-lens review (lens 1
|
||||
retail-faithfulness vs the cited decomp anchors; lens 2 architecture —
|
||||
GUI-class standardization, Runtime ownership boundaries, no
|
||||
controller-side state) → fix round → full hermetic suite green.
|
||||
Commits to the worktree branch as slices land; **no gitea push until
|
||||
the owner directs it**.
|
||||
Loading…
Add table
Add a link
Reference in a new issue