fix(ui): button property 0x0D was never "disabled", and it killed every Journal button

Reported symptom: Abandon, New, Record, Start, First and Last all unclickable.
That Abandon was in the list is what identified it — Abandon is deliberately
unwired, so if it behaved the same as the others the cause could not be wiring.

UiButton read authored property 0x0D as "starts disabled" (Enabled = !0x0D).
It was the one property read in that file with no citation, and it was wrong.
Every button on the Journal panel authors 0x0D, so every one built disabled:
visible, because drawing never consults Enabled, and unclickable, because
UiElement.HitTest skips disabled elements. Exactly the reported shape.

The evidence is a sweep of every installed layout (LayoutDump gained --ghosted
for it): 85 elements author 0x0D and ALL 85 author it TRUE — not one False
anywhere in the client — and no panel ever clears it, the only four
SetAttribute_Bool(.., 0xd, ..) sites in the binary being chargen appearance,
the keymap option and the barber. A flag that is only ever true, never cleared,
and sits on New, Record, Start, Delete and Reset cannot mean "dead button";
under the old reading 85 elements were permanently dead in a shipping game.

It is not a pure ghosted LOOK either, which is why this ignores it rather than
moving it to appearance: the same 85 mix live buttons with inert column headers
("Contract", "Status", "Title", "Timer", "Label", "#"), and one appearance
cannot be right for both. Registered as QJ-2 with the measurement, so the open
question is recorded rather than quietly decided.

The test that asserted the old behaviour carried no citation either — it
encoded the same assumption. It now asserts the evidenced behaviour, with a
companion test proving the state machine's own Ghosted transition still
suppresses a click: that mechanism is separate and did not change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-21 15:58:03 +02:00
parent c5cc8ae5fc
commit 73a04244e7
4 changed files with 84 additions and 4 deletions

View file

@ -497,6 +497,7 @@ equivalence argument (promote to AD/AP) or a fix.
| CT-5 | A bare `@log` filename lands in the client's own log directory (`ApplicationPathSet.LogsDirectory`), not the install directory retail names ("a log file named Aclog.txt in your Asheron's Call directory"). Rooted paths are honoured verbatim, as retail's `fopen` would | `src/AcDream.App/Net/LiveSessionRuntimeFactory.cs` (`_chatLogDirectory`); `src/AcDream.Core/Chat/ChatSessionLog.cs` | acdream's launcher replaces the install directory atomically on update, so a log written there is wiped by the next update or blocks it outright. Retail had no updater with that property. The client's own data directory is the equivalent that survives | A player following retail-era instructions looks for the file next to the executable and does not find it. The `/log` reply names the file, not the directory, so the path is discoverable only from this row and the code | `ClientCommunicationSystem::StartCopyOutputToFile @0x0057C8A0`; help text at `DoSetOutputHelp @0x0057A950` |
| CT-6 | The `@log` file records the composed line WITHOUT retail's inline text-tag markup. Retail's `fprintf` runs before glyph parsing, so its logs contain literal `<Tell:IIDString:…>` markers around tagged names | `src/AcDream.App/UI/ChatTranscriptLogWriter.cs` | acdream never puts markup in the line: `ChatVM` carries tags as SPANS beside the text (CT-A2/A3), so there is no markup at that seam to preserve. Reconstructing it purely to write it to a file would be inventing a string the client does not otherwise produce | A log diffed against a retail-era log differs on tagged lines — acdream's are the clean ones. No in-client effect | `ClientSystem::AddTextToScroll` write at `@0x00563E5B`, upstream of `UIElement_Text::InqGlyphs @0x00468EA0` |
| QJ-1 | The per-character journal file lives in the client's own data directory (`{data}/journal/Journal-{server}-{character}.txt`), not beside the executable where retail's sits | `src/AcDream.App/UI/JournalPersistence.cs`; path composed in `InteractionRetainedUiComposition` | Identical reasoning to CT-5: acdream's launcher replaces the install directory atomically on update, so a journal written there is destroyed by the next update. The file NAME follows retail's own `"%s%s-%s-%s.txt"` pattern exactly | A player migrating a retail journal must copy the file rather than find it picked up in place. No in-client effect | `gmJournalUI::LoadPages @0x00496AC0` / `SavePages @0x00497270` |
| QJ-2 | Authored button property `0x0D` is ignored. Retail's `UIElement_Button::UpdateState_ @0x00471CF0` reads it and selects the Ghosted visual state when set; acdream reads it for neither input nor appearance | `src/AcDream.App/UI/UiButton.cs` (constructor) | It CANNOT mean input-disabled: measured across every installed layout, 85 elements author `0x0D` and all 85 author it TRUE, never False, and no panel clears it (the only `SetAttribute_Bool(.., 0xd, ..)` sites are chargen appearance, the keymap option and the barber). Reading it as "disabled" made every Journal-panel button visible-but-unclickable. Nor can it be a pure ghosted LOOK: the same 85 include live buttons (New, Record, Start, Delete, Reset) alongside inert column headers, so one appearance cannot suit both | If `0x0D` turns out to drive appearance, the affected elements render un-ghosted where retail greys them — 85 elements, mostly column headers. No input or state effect | `UIElement_Button::OnSetAttribute @0x00471F40` case 0; `UpdateState_ @0x00471CFC` |
---