Commit graph

681 commits

Author SHA1 Message Date
Erik
c5f73744f0 docs(fa3): update plan ledger with fix-round SHAs and final totals
Records the FA3 fix-round closure: both dual-lens reviews' findings
(mechanism 2 MUST-FIX/9 SHOULD-FIX, blast 1 MUST-FIX/6 SHOULD-FIX/1 NIT)
applied across four commits (9afa05b5/35c40a9b/ae772709/a5553904).
Baseline 13,233/4/0 (13,237 total) -> fix-round 13,238/4/0 (13,242
total), +5 tests, arithmetic verified directly against all 9 test
project totals. FA3 still owes the user's connected gate against the
now-corrected script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:46:10 +02:00
Erik
35c40a9b56 fix(ui): allegiance page — hoist per-frame LinesProvider allocation, document the coarser empty-state gate
Mechanism SHOULD-FIX 2 / blast SHOULD-FIX 4 (same finding, both reviews):
SocialAllegiancePageController.Tick() closed over a local `lines` on
every call — `() => lines` allocated a display class plus a delegate on
EVERY frame, unconditionally, at ~2,000 allocations/second at the
profile's measured FPS, in a hot loop the Modern Runtime slices spent
whole commits driving to 0 B/frame. Hoisted two static readonly
Func<IReadOnlyList<UiText.Line>> providers (BlankLineProvider/
NoLinesProvider); Tick() now assigns the cached delegate reference —
zero allocation while idle or active.

Mechanism SHOULD-FIX 7: FA3's empty-state gate is coarser than the
retail mechanism it is contracted against — gmAllegianceUI::UpdateMonarchData
@0x00491B40 hides the monarch/patron blocks per-relationship (monarch
block also hides when the monarch IS the viewer; patron block on the
analogous test), while this shell gates both blocks on the single
HasProfile flag. Not a MUST-FIX for FA3 (MF-2 in the same review means
HasProfile is effectively always false for the whole FA3 gate, so
nothing wrong is visible during this slice's own gate) — recorded
instead as an explicit FA5 acceptance line in the plan's FA5 row so the
gap cannot be silently lost, plus a class-doc note that FA5's real
monarch/patron population must also change Tick()'s unconditional
LinesProvider reassignment in the same commit or its content will be
overwritten the next frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:43:48 +02:00
Erik
b6a25110f3 docs: FA3 -- connected-gate test script + plan ledger update
- docs/research/2026-08-12-campaign-fa-test-script.md: the user's
  connected-gate script for FA3 -- open paths (F3/F4 + tab switch),
  gmPanelUI exclusivity vs sibling panels, tab switching, all four
  pages' expected shells/empty states, Friends/Squelch read-only
  expectations + the D1 INERT buttons, what to report, what's
  explicitly out of scope (FA4/FA5/FA6/D1's deferred wire).
- Plan ledger: FA3 row filled in with commit SHAs, totals (13,233/4/0,
  13,237 total, +18 over FA2's close), the tab-table correction finding,
  and the reverted unrelated fixture drift note. Campaign status line
  updated from "FA3 in flight" to implementation-complete pending
  review + gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:59:10 +02:00
Erik
b560f415cd docs: FA2 CLOSED — re-review verdict + CF-1 correction to the FA5 slice row
The narrow re-review (cc1a319c) closed FA2 with no reopen; its
carry-forward corrected the FA5 row: the allegiance page's data
subscription is 0x001F AllegianceUpdateRequest(on) at retail's three
arming points, not the text-only 0x027B info request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:31:27 +02:00
Erik
ed8b3ec96d docs: FA2 fix-round -- ledger update, D2 correction, seam-map addenda
Plan (docs/plans/2026-08-11-fellowship-allegiance-campaign.md):
- D2 gets the D9-style dated strike/addendum recording the corrected
  allegiance reset semantics (clears at every generation reset; the
  HasServerSeed latch only gates pre-seed rendering WITHIN a session) with
  the three-way evidence citation: the retail OnEndCharacterSession hook,
  the RuntimeCharacterOptionsState precedent's actual clear-and-relatch
  behavior, and the no-character-selector connect path
  (SessionPlayerComposition.cs:1127).
- The architecture blurb and FA2's slice-map contract row get matching
  strike/addendum corrections so the "fellowship session-scoped,
  allegiance survives reconnect" claim does not survive uncorrected
  anywhere in the plan.
- FA2's ledger row: fix-round commit SHAs, corrected delegate-hole/
  wrapper counts (blast SHOULD-FIX 3: 10 not 15, 11 not 12), the
  allegiance register-row re-evaluation conclusion (blast SHOULD-FIX 6 --
  no row needed, MF-1's fix retires the deviation entirely), and the
  reconciled fix-round test totals (13,201/4/0 -> 13,215/4/0, +14,
  arithmetic exact per file).

Seam map (docs/research/2026-08-11-fa-acdream-seams.md), per the FA1
fix-round's established in-place-correction convention:
- SS1.3 and SS9's dispatcher-replaces-not-chains correction is now dated
  and cites the actual GameEventDispatcher.Dispatch behavior, matching the
  code comment already landed in GameEventWiring.cs.
- SS2.3 gets the 0x01C9/0x01CA disposition it was missing (correctly
  left unregistered -- dead COMDAT-fold no-ops per FA1) so FA3 does not
  have to re-derive it or "fix" the gap.
- The SS8 seam-map table's Allegiance-owner row and the executive-summary
  ownership bullet both get the "survives reconnect" claim struck with a
  dated correction to "session-scoped, clears at every generation reset".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:18:59 +02:00
Erik
12053e61ea docs: FA2 -- divergence register TS-81/TS-80 + campaign ledger update
TS-81 filed: 0x027A AllegianceLoginNotification's retail two-line chat
text is not emitted -- the candidate strings resolve through Binary-
Ninja-mislabeled symbols at gmAllegianceUI::RecvNotice_AllegianceLogin
(0x00492220) that need a DAT string-table lookup, not a guess.
TS-80 partially narrowed: the fellowship-create shareXp wire mechanism
now exists end-to-end (IRuntimeFellowshipCommands.Create takes and
sends it), but no caller reads the option bit yet -- that's FA4's
create-dialog scope.

Updates the campaign plan's FA2 ledger row: code-complete, full test
totals (13,158/4/0 -> 13,201/4/0, +43 exact), the seam-doc dispatcher
correction, and the entity-table-borrow recommendation that wasn't
needed (the wire's own FellowMember record already carries full vitals
inline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 01:32:39 +02:00
Erik
1bb707e248 docs: FA1 CLOSED — re-review CF-1 seam-map addenda + ledger verdict
The narrow re-review (96df892d) closed FA1 with no reopen and one
carry-forward: two further forward-looking seam-map rows (:128 owner
diagram, :214 state-parameter pattern) still cited the deleted
AllegianceTree as FA2 design guidance. Both now carry dated strike/
addendum notes pointing FA2 at the parsed profile records instead.
FA2 unblocked per the re-review's precondition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:56:18 +02:00
Erik
511ba6e5d4 docs: FA1 review round -- register repoint, seams corrections, plan reconciliation
Applies the documentation-only MUST-FIX items from the blast review
(docs/research/2026-08-12-fa1-review-blast.md), plus mechanism SF-4:

Blast MF-1 / mechanism SF-4: register row AP-90
(retail-divergence-register.md) cited the deleted AllegianceTree class as
its evidence. Re-pointed to ClientCommandResponses.AllegianceProfileLookups
and the fellowship parsers FA1 added -- the deviation itself (radar
relationship state undelivered at runtime) is unchanged and NOT retired,
since FA2 hasn't wired a live owner yet.

Blast MF-2: corrected four falsified statements in the lane D research doc
(fa-acdream-seams.md), each marked with a dated, clearly-struck FA1
fix-round addendum rather than silently rewritten (it is a committed
research record):
  - :791 "wrapping existing AllegianceTree" -- class deleted; re-pointed to
    AllegianceProfileLookups.
  - :666/:672 `commands.Fellowship.SetOpen -> BuildFellowshipUpdate` -- that
    builder no longer exists; its renamed successor is panel visibility,
    not openness, and using it here would re-introduce the exact semantic
    bug FA1 fixed. Re-pointed to BuildFellowshipChangeOpenness (0x0291).
  - :429/:668 `BuildFellowshipCreate(seq, name, openness, shareXp)` -- the
    builder is now 3-arg; there is no wire openness field.
  - :854 open question 8 (trailing-pad rule) -- ANSWERED by FA1 (VC-3),
    closed with the answer instead of left open for re-derivation.

Blast MF-3: plan decision D9 and the FA1 slice-map row both asserted "the 8
missing fellowship WeenieError strings are added in FA1" -- FA1 shipped the
opposite, verified finding (no retail display text exists for any of the
8 ids). Both struck and annotated with the actual outcome.

Blast MF-4: reconciled the ledger's internally-inconsistent test-total row.
Direct measurement at the pre-fix-round tip (bc693728, stashed/restored
during this session to isolate it) confirms 13,149 passed / 4 skipped / 0
failed (13,153 total) -- the ledger's own prior number was actually
correct; the "baseline 13,103" and "net +50" framing next to it did not
reconcile with each other or with the diff-verified delta (+58 added / -9
deleted = net +49, one test of drift attributed to a different baseline
commit, not a further miscount). Also records this session's own +9 tests
and the blast SF-1 live-surface note (FA1 changed observable @allegiance
info output, not a purely-unwired slice).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:47:09 +02:00
Erik
ee1124ca5b docs: FA1 ledger entry -- Core.Net truth slice code-complete
Records the FA1 commit SHAs and automated-gate test totals in Campaign
FA's ledger (§9). Owes its dual-lens review per the campaign's §7
protocol before FA2 (the RuntimeFellowshipState/RuntimeAllegianceState
owners) begins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:14:09 +02:00
Erik
bb48d2c89d plan: Campaign FA — the retail social panel (Fellowship & Allegiance)
Seven slices over the four committed research lanes + the U2 slot-table
closure: Core.Net truth (repair/complete/delete the H.2 scaffolding),
two sibling J-owners with different lifetimes, the OP3-recipe mount of
the ONE four-tab social panel (id 12: Friends/Allegiance/Fellowship/
Squelch), the two live pages, bot-vs-ACE gates (second ACE account is
the user prerequisite at FA6), and closeout. Nine design decisions
stated (D1-D9); Campaign OP's gate lessons imported as binding rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:44:21 +02:00
Erik
5eca35b706 docs: park Campaign OP — gate-4 fixes committed, user re-check owed at resume
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:11:43 +02:00
Erik
07c0c2c7b9 docs: Campaign OP CODE-COMPLETE — plan/script/CLAUDE.md status flips
The campaign's /goal stop condition is reached: all nine slices landed and
reviewed (OP1/OP2/OP7/OP9 CLOSED; OP3-OP6 + OP8 code-complete with
connected gates owed), and the gate script is the complete per-tab
connected-gate contract (launch with ACDREAM_RETAIL_UI=1; §OP7 already
PASSED live bot-vs-ACE). OP9's ledger row records the combined review
chain (289bf5bc APPROVE-WITH-FIXES -> residuals 07f2b3f7) including
SF-4's corrected test-delta arithmetic (-84, not the implementation
commit's '-80 exactly'). CLAUDE.md Current-state gains the campaign
paragraph per feedback_claude_md_staleness; the settings digest
(claude-memory/project_settings_options_digest.md) is the new domain
entry point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 13:43:33 +02:00
Erik
1a57f96efe docs: OP8 merged — ledger CODE-COMPLETE, file #373 (ConflictingMaps)
Merge 1c5cd969 lands OP8 on the campaign tip after 057d8cd7, so the
#372 viewport fix covers OP8's six ListBoxes (the re-review's merge
precondition). Post-merge full Release suite: 13,155 / 4 skips / 0
failures. #373 captures the deferred DAT ActionMap.ConflictingMaps
consultation the OP8 round-2 conflict-universe fix scoped out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 12:42:25 +02:00
Erik
b4edee970f feat(ui): Campaign OP slice OP8 — Configure Keyboard
Ports retail's Configure Keyboard screen (gmKeyboardUI, LayoutDesc
0x21000009) — its own separate full-screen window, not a fifth Options-
panel tab. Retires OP3's INERT contract for the Gameplay tab's Configure
Keyboard button (0x10000204).

DAT reader (src/AcDream.Core/Input/RetailActionMap.cs): reads the
ActionMap singleton (DID 0x26000000, empirically the only one — not
0x27000000 as GetDBOType's Turbine-internal tag would suggest) and both
MasterInputMap defaults (0x14000000 "gmDefaultMap"/0x14000002
"DefaultMap"), union-merged per (InputMapId, ActionId) — proven order-
independent since the two maps' one shared context (0x5) has disjoint
action-id sets. Empirically resolved three lane-D unknowns against the
live DAT: the six ActionClass values (1=Movement, 2=Camera, 3=UI,
4=Combat, 5=Emote, 7=CharacterSettings — 6 is genuinely absent), that
the six unnamed InputMaps are 100% non-bindable (render nothing, not an
unlabeled group), and that the enum-to-DID pairing for the two master
maps is inconsequential to the merge result.

Identity table (src/AcDream.UI.Abstractions/Input/RetailActionIdentityTable.cs):
maps DAT (InputMapId, ActionId) pairs to acdream's InputAction where a
live consumer exists (~140 of 306 user-bindable rows — Movement/Camera/
Combat map almost completely; UI/Quickslot/Chat partially; only 5 of 87
Emotes and none of 48 CharacterSettings hotkeys, since acdream has no
general emote player or hotkey-to-option-toggle dispatcher yet). Every
entry cross-verified by label match AND a DAT-default-vs-
KeyBindings.RetailDefaults() byte comparison (RetailActionIdentityRoundTripTests),
which caught a real off-by-one in the Quickslot 13-18 block before it
shipped and found three genuine pre-existing RetailDefaults() gaps
(walk-mode's Shift-echoed chord, ten CameraAlternateControls arrow-key
alternates, and the Quickslot Ctrl+N use-vs-select ambiguity) — none
introduced by this slice, all documented rather than silently patched.

KeyboardConfigController: six ActionClass list boxes built from the
DAT, merged with live KeyBindings for mapped rows (rebind applies
immediately through the same InputDispatcher every other input path
uses) and a new sibling RetailUnmappedKeyBindings store for rows with
no InputAction yet. Left-click a key button opens real InputDispatcher
modal capture; right-click erases that slot. N-way conflict detection
scans every other row plus the live KeyBindings table for acdream-only
actions (Ctrl+M mute, debug F-keys) as the non-user-bindable refusal
analogue, using retail's own byte-verified "Could not overwrite "
string (table 0x23000004). OK/Cancel/Defaults/Revert reuse the
OptionPage/IOptionRow verb model via a new ActionKeyMapOptionRow.
Persistence is keybinds.json only (D4 — no .keymap file interchange).

Five register rows: AP-202 (.keymap interchange narrowing), AP-203
(store-only rows with no live consumer), AP-204 (silent auto-reassign
instead of retail's confirm dialog; OK/Cancel ported as left-click not
right-click-release).

Small supporting additions: UiButton.OnRightClick (additive, no
existing behavior changed), InputDispatcher.Bindings getter (the
screen's single live-truth read seam), RetailScanCodeMap (DIK scan
code <-> Silk.NET Key, keyboard + the one mouse-device row).

19 new tests (6 ActionMap reader conformance incl. live-DAT row-count/
label pins, 1 DAT-vs-RetailDefaults round-trip, 12 controller
behavior tests against the committed keyboard_config_21000009.json
fixture) — full solution suite 13,147 passed / 4 skipped / 0 failed
(baseline 13,128/4/0, zero regressions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:19:54 +02:00
Erik
ff5776415b docs: OP6 re-review CLOSED — slice code-complete, gate ready; doc residuals
The re-reviewer byte-decoded ALL six caption sites independently (no
transposition; a third evidence line from the fixture's authored
left/right label geometry), re-verified the audio chain through the one
place an inversion could still hide (the checkbox pass-throughs), and
hand-traced the 97-key conformance table (exactly 97, none invented or
dropped). Residuals applied here: SF-1 the AudioSettings doc comment's
wrong function attribution (the SetDefaultValue literals live in
gmConfigUI::InitOptions @0x0049E435/E457/E479, not InitUIPreferences);
SF-2 the gate script no longer asserts an unread DAT caption — it gates
on behaviour and asks the tester to report the authored English
verbatim; lane A's stale '24 option rows' corrected to 27 (the 39-item
pin is the authoritative tally). AudioSettings.cs change is
comment-only (no executable-code delta; suite state carries from
67b0815c, re-verified at the next code commit).

Campaign state: OP1-OP7 ALL CODE-COMPLETE; OP3/OP4/OP5/OP6 gates ready;
OP8 (Configure Keyboard) is the sole remaining implementation slice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 08:21:07 +02:00
Erik
9714e491aa docs: ledger — OP5 code-complete (gate ready), OP6 rework awaiting re-review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 08:13:39 +02:00
Erik
6097d2d136 docs: ledger — OP4 code-complete (gate ready), OP5 landed pending review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 06:36:29 +02:00
Erik
7e9f372ce8 docs: OP7 live bot-vs-ACE gate PASSED — slice CLOSED
Evidence appended to the test script: run-1 diff-and-send exact to
contract; mid-run reconnect idempotence; run-3 cross-process persistence
proof (the fresh seed echoed the blob-only SalvageMultiple value);
graceful converged exits; no refusals, no pre-LoginComplete sends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 03:31:23 +02:00
Erik
e1e4865439 docs: ledger — OP3 code-complete (gate ready), OP7 code-complete (live run owed)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 03:25:54 +02:00
Erik
5242de9f15 fix(ui): OP2 re-review closure (coordinator pass) — AP-195, tooltip port, zero-children pin
Closes the mechanism lens's REOPEN (one MUST-FIX) and both lenses' small
residuals on the OP2 rework (b236a442); the blast lens re-review was
CLOSED outright. Fable-direct per the two-failure escalation rule.

- AP-195 filed: UIOption_CheckboxBitfield64 ports HALF of Refresh
  @0x004859C0 — the ANY-set checkbox predicate is exact, but the ALL-set
  LED media swap (P0x10000082=0x06004D17 / P0x10000083=0x06004D19) and
  the ListBox self-sizing tail (ResizeTo/CalculatePaperSize — the block
  IS a UIElement_ListBox in retail) are unported, and the block's row
  stacking is a second divergent implementation beside UiTemplateListBox.
  All due at OP5 before the Chat tab's connected gate; the IsSet doc
  comment now names both halves instead of quoting only the ported one.
- Row tooltips: UiButton gains settable TooltipText surfaced through the
  shared GetTooltipText hover pipeline (UiCatalogSlot's pattern);
  UiCheckboxBitfield64.AddChild applies the row tooltip retail stamps in
  CreateChildren @0x00485DF0, and documents that the 0x10000084 row-index
  attribute stamp is deliberately replaced by the typed mask closure.
- AD-73 addendum: UiTemplateListBox.ConsumesDatChildren=true is inert
  only while no authored Type-5 element carries children — that premise
  is now conformance-PINNED across all 32 fixtures (a future DAT
  regeneration surfacing an authored child fails the build instead of
  silently dropping it).
- Plan doc: OP2's contract names UiTabPanel.cs (retail UIElement_Panel),
  not the fictional-class-named UiTabControl.cs; ledger records OP1 and
  OP2 both CLOSED.

Full Release suite: 12,871 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 01:26:29 +02:00
Erik
8a05fda445 docs: OP1 re-review CLOSED — ledger records slice OP1 complete
Focused re-review verifies all ten fix dispositions genuinely close their
findings (fellowship order re-derived from OnChanged @0x0059A8E0; the
seed latch confirmed upstream of every one of the three blob call sites;
TS-71 retirement and TS-73 accuracy checked). Residuals owed to a small
coordinator pass, none reopening: R1 closure hoisting defeats the
per-tick allocation guard (move the IsDirty check into the ctor
lambdas); R2 concurrent MarkDirty during a flush is silently erased by
the trailing clear (needs the dirty-generation token); R3 NOTE — a
truncated-trailer PlayerDescription (options words zero) arms the seed
latch, newly wire-reaching via the timer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 00:56:36 +02:00
Erik
b585d80e7a docs: Campaign OP plan — nine review-gated slices for the retail Options panel
Fable-authored plan from the four seam-verified research lanes. Eight
design decisions stated per the campaign directive (reactable at gates):
the retail panel is acdream's ONE in-client settings surface (the F11
SettingsPanel was never rendered post-V11 — lane D's verified correction),
retail's 21-vs-batched wire split ships exactly with all three flush
triggers, the 2015-only PK-deaths row ships wire+store with a register
row, Configure Keyboard is the campaign's only rebind screen (DAT
ActionMap data, keybinds.json persistence), dead support-URL buttons
short-circuit to their own byte-verified retail failure strings, Exit to
Character Selection adapts to Exit Game behind retail's confirm dialog +
mid-air refusal, lane B group C re-points to server truth per CH3
precedent, and bots declare options by NAME in the K1 strict schema.

Slices: OP1 Runtime map/dirty/blob (+ the headless local-write fix both
lanes found), OP2 the two missing widget primitives + UIOption mappings,
OP3 shell + Gameplay tab vertical, OP4 Character tab, OP5 Chat tab, OP6
Config tab, OP7 headless characterOptions, OP8 Configure Keyboard, OP9
closeout + connected test script. U1 closed during planning: the Defaults
button restores DAT DBPropertyCollection values
(UIOption::InqDefaultGameplayOptionProperty @0x004ef8d0,
GetDIDFromEnumStatic(0x16, 2)).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 22:52:16 +02:00
Erik
aa6635aebf fix(ui): round-5 review polish — S1 block outline pass, S2 non-UiText outline paths, S3 citation fix
Collects the post-gate polish left uncommitted by the killed round-5 agent
(S1/S3 + review fixes N1/N3/N4) and completes the missing S2 half:

- S1: UiText multi-line transcript + colored-run label now submit EVERY
  line/run's outline pass before ANY fill pass, matching retail's
  UIElement_Text::DrawSelf @0x00467aa0 whole-block walk. DrawStringDatPass
  is exposed for block-level batching; single lines keep DrawStringDat.
- S2 (completed this commit): authored outline 0x21/0x22 now reaches every
  text-bearing widget — UiButton, UiDatElement, UiField, UiMeter, UiMenu,
  UiCatalogSlot — seeded from the element's effective-default state exactly
  like UiText (BuildButton lifts the label-bearing Text child's authored
  value first, same chain as the label color). Per-STATE outline switching
  (dialog/character/combat buttons author 0x21 in state 0x3 only) is NOT
  ported — filed as register row AP-192 in this commit.
- S3: ChatWindowController reconciliation comment corrects the misread
  indicator action ids 0x10000514-17 -> 0x10000114-17 and re-attributes the
  id-coincidence to the pagination widget's m_prevButton/m_nextButton, not
  gmFriendsUI; register + window-shell research doc corrected to match.
- N1: LayoutImporter's duplicate per-state any-state-first-wins 0x21 read is
  deleted — ElementReader.ApplyCanonicalLegacyProjection's DirectState-then-
  effective-default resolution is the single source (the duplicate would have
  lit state-0x3-only outlines permanently once S2 widened consumption).
- N3: the outline pass tints with the outline color's OWN alpha, not the
  fill's (retail tints m_curOutlineColor and m_curTextColor independently).
- N4: the outline-inflated glyph SOURCE rect is clamped to the atlas bounds
  with matching dest shrink, porting CreateCharRectPair @0x00441480's edge
  behavior — edge glyphs crop instead of sampling a neighbour's texels.

Full Release suite: 12,610 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 22:15:20 +02:00
Erik
5f2a7d7898 docs: Campaign CH CLOSED USER-ACCEPTED — five gate rounds to acceptance
Round 5 (2026-08-10): 'Good, looks good now.' Carried tail recorded in
the plan header and CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:48:16 +02:00
Erik
bcc34ee301 feat(chat): retail text style — two-plane glyph outlines, authored SpewBox/chat styles
Campaign CH round 4, user-gate items 1+2. Root cause: retail ships a
second (background) glyph atlas per font, dilated 2px on every side,
plus two border-pixel scalars (Font.NumHorizontalBorderPixels/
NumVerticalBorderPixels) that acdream's font reader never read — so
even the pre-existing outline parameter drew almost nothing once
enabled. Landed together (either half alone is a no-op or a
regression):

- UiDatFont carries BorderX/BorderY from the DAT font resource.
- UiRenderContext.DrawStringDat inflates the background blit's source
  and destination rect by that margin and restructures into retail's
  exact two-pass whole-string outline-then-fill model
  (UIElement_Text::DrawSelf), plus the 8-neighbour +-1px fallback for
  fonts with no background atlas. Corrects the stale "property 0xd"
  comment to the real ids, 0x21 (Outline) / 0x22 (OutlineColor).
- LayoutDesc property 0x21/0x22 import (ElementInfo.Outline/
  OutlineColor, LayoutImporter.ReadState, ElementReader.Merge/
  ApplyCanonicalLegacyProjection, DatWidgetFactory.BuildText) so every
  authored-outline element across the DAT set is correct at once.
- SpewBox: RetailFontId corrected from a round-3 heuristic
  (0x40000025) to the actually-authored 0x40000001 (18px bold serif),
  Outline=true set on the controller's UiText. Fill colour stays the
  user-gate-round-1-pinned yellow — font atlases are alpha-only
  (PFID_A8), so there is no baked shading that could explain the
  screenshot's gold as anything other than the outline itself.
- Chat transcript: default fill now seeds from its authored
  ARGB(255,204,204,204) instead of an unrelated color-table slot
  (ChatTranscriptRenderer.BuildLines takes the transcript's own
  DefaultColor as a parameter); the 34-entry LogTextType table is
  untouched, and every existing CH1 conformance test stays green
  unmodified.

Regenerated the committed chat_2100006f.json fixture from the real
installed DAT, confirming end to end (not by missing-field default)
that the transcript carries no outline.

Tests: font-reader border fields + inflation math pinned against the
real DAT font, two-pass draw ordering/tint/inflation via a new
TextRenderer.DebugSpriteSegmentVerts test seam, property 0x21/0x22
import at both the ElementReader.Merge and StateDesc-property layers,
SpewBox font/outline, and the chat default-shade seed with the color
table proven untouched.

Full Release suite: 12,610 passed / 4 skipped / 0 failed
(AcDream.slnx, complete solution).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 19:28:34 +02:00
Erik
5b54387b8e fix(chat): round 4 — no user-visible meta text, real /help groups, indicator buttons toggle
Item 3 (#364): every honesty marker is now gone from user-visible /help
text. AllegianceOverview/HouseOverview's "[IMPLEMENTED]" tags and trailing
"Subcommands NOT marked..." sentences, and Day/Log/Render/Motd's appended
"NOT YET IMPLEMENTED in acdream" tails, are removed; the underlying retail
text is corrected/completed against the pseudo-C's own pristine
consolidated data dumps (Log and Motd had been silently truncated; Render
was entirely acdream-authored and is replaced with the real retail usage
string). The three PARTIAL /help group topics (channels/chatting/commands)
are now COMPLETE verbatim listings: HelpStupidChannelHack's three
"vtable slot" operands, previously believed undecodable, are the same
pooled/mislabeled-data artifact this campaign has hit before (AP-113's
precedent) — reading the function's own disassembly for the push imm32
preceding each constructor call resolves all three directly. messagetypes
is now a real ported construction (IsLegalChannel's 14-id whitelist +
LogTextTypeToString's name table + the exact join/wrap format) instead of
an acdream summary. Register row AP-184 retired.

Item 5: the main window's 1/2/3/4 indicator buttons now toggle their
floating chat window on click, per the user's retail memory overruling
the earlier decomp-only reading. UIElement_Button::HandleButtonClick has
its own generic click-driven action dispatch (property 0x12) reaching the
same DoVisibilityToggleAction the Alt+1..4 keybinds use; the button
fixture confirms this half is genuinely armed, but the floating-window
fixture authors no matching listener-registration property, so the
generic mechanism has no proven target in the data on hand. Per
CLAUDE.md, the user's retail memory is the axiom regardless:
ChatWindowController.BindIndicatorClicks wires each indicator's click
through the same ToggleFloatingChatWindow chokepoint the keybinds use,
as explicit user-directed retail behavior. SetIndicatorOpen stays the
sole writer of the Selected mirror so the visual stays consistent
through the click round trip.

Full reconciliation in docs/research/2026-08-09-chat-retail-window-shell.md
§1.4. Campaign plan gets the round-4 findings section; items 1+2
(text-style) are under parallel research, item 4 passed, item 6 deferred
to the settings track.

Suite: 12,579 passed / 4 skipped / 0 failed (Release, complete solution),
up from baseline 12,553/4/0 — net +26 tests, zero regressions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:46:34 +02:00
Erik
8b166f3ea2 docs: goal-window closeout — consolidated-review fixes at f7a6f46b; round-4 gate next
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:23:23 +02:00
Erik
f7a6f46ba0 fix(chat): consolidated-review fixes — retail /help Detail extraction, seam wiring test
SHOULD-FIX 1: RetailClientCommandCatalog's ~45 catalog leaf verbs were
showing acdream-authored Summary text for /help <verb> instead of
retail's own Detail_HelpType(2) text. Byte-swept every Help* handler
against the PDB-paired acclient.exe (verified MATCH), confirmed each
Detail/Summary branch by reading the actual decompiled if/else shape
(address order and string length both proved unreliable alone), and
fixed a sweep_weenie_strings.py 800-char truncation bug that silently
dropped several longer Detail branches. Resolved every ambiguous
CmdHashData-registered verb (hor/hr/hom/hoa/alh/ah/friends_add/
friends_remove/squelch/unsquelch) by reading for Binary Ninja's
nullptr-4th-arg decompiler artifact instead of trusting it. Coverage:
42 of 47 distinct catalog Definitions verbatim-extracted, 4
confirmed-null (index/clist/on/off register with a genuinely null help
pointer — DoHelp falls to UnknownCommand for these, now reproduced),
1 honest UNVERIFIED (messagetypes builds its text from a runtime enum
table, not a static string). ChatCommandRouter now prefers retail
Detail text over the catalog summary; RetailCommandHelpTable's class
doc no longer overclaims its own scope.

SHOULD-FIX 2: extracted the a5a7eb4f-class OnInterfaceText wiring into
a testable CreateChatViewModel method and added
ComposedChatViewModelWiresOnInterfaceTextToSpewBox, which the prior
FakeFactory-based test suite could never exercise.

SHOULD-FIX 3: retires register row AP-113. DoLifestone/DoMarketplace
print their own 0x1A refusal text (byte-recovered, UTF-16LE) instead
of falling through to the generic 0x26 fallback; ChatCommandRouter's
comment corrected to state the fallback's real scope.

SHOULD-FIX 4: corrected the divergence register's stale AP section
header sentence about AP-190's opacity default (refuted by cc582899).

NITs: (a) HeadlessStaticStateAudit routes through the injected
HeadlessDiagnosticWriter instead of Console.WriteLine; (b) a bounded
300-pump liveness diagnostic on the IsQuiescent conductor gate (no
retry, no behavior change); (c) fixed the #365 hydration test's doc
comment contradiction against diagnosis §8; (d) the 0x26 fallback
dispatches on WeenieErrorMessages' own Type instead of hardcoding
ClientLocal.

Full Release suite: 12,553 passed / 4 skipped / 0 failed (baseline
03404b71: 12,542/4/0; net +11 tests, zero regressions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:22:36 +02:00
Erik
03404b7121 docs: #363 ledger SHA (09453eca)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 15:24:05 +02:00
Erik
09453ecae8 fix(chat): #363 — retail 0x1A typing for command refusals via the interface-text seam
ChatVM gains an OnInterfaceText hook + ShowInterfaceText(text), the
App-layer composition wires it to RuntimeCommunicationState.AddText,
and ChatCommandRouter routes every retail-0x1A command refusal through
it instead of the chat log's 0x00 sink. UI.Abstractions still never
references Runtime directly; unwired hosts (headless, tests) fall back
to the chat log tagged ClientLocal so no text is ever silently lost.

Reclassified per register row AP-183 (DoChannelList/On/Off, DoAllegiance,
DoHouseAvailableList — the last also corrected to retail's own bad-house-
type string instead of a synthesized "Usage:" line) and newly wired two
sites that previously showed nothing at all (DoStupidChannelHack's bare
legacy-channel-verb refusal, DoReply's message-but-no-last-teller
refusal). The generic bad-args fallback now resolves WeenieErrorMessages
0x026 ("That is not a valid command.", retail's HandleFailureEvent(0x26))
instead of synthesizing "Usage: {Usage}". DoSpeaker/DoEndurance/DoTitle
are untouched — already correct at 0x00.

Also closes #367 (DoHelp's "Unknown command" fallback and the degenerate-
prefix refusal now reach the SpewBox too) and retires register row
AP-186, whose own filing proposed exactly this seam shape.

Full Release suite: 12,542 passed / 4 skipped / 0 failed (baseline
12,466/4/0 at ff2784ea).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 15:21:30 +02:00
Erik
cc58289967 fix(chat): CH6c review fixes — opaque default, opacity-transition register clauses
BLOCKER: ChatSettings.DefaultOpacity shipped retail's base ChatInterface
value (0.5) as ONE shared global default applied to every
RetailWindowManager-registered window, not just the four floating chat
windows retail itself fades. That faded the whole out-of-box registered
UI (radar, vitals, toolbar, main chat, ...) to 50% opacity, including
several windows that can never take keyboard focus and so were stuck at
0.5 permanently. Fixed to gmMainChatUI's 1.0/1.0 override
(0x004CD0F0) instead — retail-identical opaque presentation for the 11
non-chat windows and the main chat window; only the four floating chat
windows now diverge from retail's 0.5-while-idle default, and the
Settings -> Chat transparency slider remains fully user-settable.

AP-190 reworded and gains two new decomp-verified clauses: (3) retail
eases opacity toward its target by 5% of the delta per tick
(ChatInterface::ListenToGlobalMessage @0x004F3840, armed from the focus
element-messages at @0x004F5275) where acdream snaps -- deferred, needs
a UI frame-tick hook the opacity controller doesn't have; (4) retail's
focus predicate is the chat ENTRY FIELD specifically
(ChatInterface::IsTextEntryFocused @0x004F30A0) where acdream uses
any-focusable-descendant. Both findings + the pre-existing UiMenu.cs
PushAlphaAbsolute(1f) popup bypass are folded into the window-shell
research doc's opacity section.

NITs: fixed the stale "text bypasses the alpha" comment in
UiElement.DrawSelfAndChildren (CH6c already routed DrawStringDat/
DrawString through the same ApplyAlpha chokepoint as sprites/rects);
added RetailWindowManager.WindowUnregistered + wired
RetailWindowOpacityController to detach and forget a window unregistered
while it held focus (previously only Dispose detached, leaking any
window unregistered mid-focus for the rest of the session); added
post-Dispose no-op guards to the three Set* opacity mutators; added a
DrawString (BitmapFont path) alpha regression test and a DrawStringDat
outline/background-pass alpha test (the existing tests only ever
exercised the foreground/fill pass).

Also fixes RuntimeSettingsControllerTests.SettingsViewModelSavePreserves
SectionAndTargetOrder's now-stale "target-chat-opacity:0.5:1" expectation
(caught by the full-suite run this fix requires) to match the new 1.0
default.

Campaign ledger CH6c row updated to APPROVE-WITH-FIXES.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:48:39 +02:00
Erik
964af62e25 docs: CH6c ledger SHA (a819687c)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:01:04 +02:00
Erik
a819687cf0 feat(chat): Campaign CH slice CH6c — window opacity + transparency setting
Retail's ChatInterface::SetOpacity (0x004F3120) fades the WHOLE composited
window surface with one alpha; UiRenderContext.ApplyAlpha already gated
DrawSprite/DrawRect/DrawFill (since 1da697ec, pre-CH6) but DrawStringDat and
DrawString still passed applyAlpha:false, so text stayed sharp over a
translucent window. Both now route through the same chokepoint.

RetailWindowOpacityController (new) subscribes to a new
RetailWindowManager.WindowRegistered event and drives every registered
window's live Opacity from keyboard-focus state, applied to EVERY window
(chat, floaties, vitals, toolbar, ...) rather than retail's ChatInterface-only
scope — register row AP-190, retiring the stale AP-40 "fixed 0.75, no focus
transition" row in the same commit.

Verified retail's shipped opacity defaults from the decomp (constructor
literals, no cdb needed): the base ChatInterface ctor sets
DefaultOpacity=0.5/ActiveOpacity=1.0, kept unmodified by the four floating
windows; gmMainChatUI's own ctor overrides the main window to 1.0/1.0
(always fully opaque). acdream ships one shared global default (0.5/1.0)
rather than replicating the per-class override — also AP-190. The linking
invariant (raising default above active drags active UP; lowering active
below default drags default DOWN — never a clamp) is ported verbatim as
ChatOpacityLink in AcDream.UI.Abstractions, shared by the live controller
and the new Settings -> Chat tab's two linked opacity sliders.

Persistence: ChatSettings.DefaultOpacity/ActiveOpacity round-trip through
SettingsStore; Save pushes both through IRuntimeSettingsTargets.SetChatOpacity
into the live controller, no restart required.

Rider (CH6a/b re-review): strengthened the grip-media regression guard past
a bare SpriteFile != 0 check — ChatLayoutConformanceTests now drives each
live grip through a real UiRenderContext/TextRenderer (backed by the
in-memory RecordingGpuDevice test double) and asserts the draw call chain
actually queued sprite geometry, via a new TextRenderer.DebugSpriteSegments
test-only accessor.

Full Release suite 12,459 passed / 4 skipped / 0 failed (baseline
12,420/4/0). No subagents, no client launches (session hard constraints);
pending the next connected user gate for visual confirmation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:00:55 +02:00
Erik
69e355bbdf docs: repair ledger SHAs mangled by the blanket placeholder replace
The '(this commit)' placeholder appeared 13 times across historical rows;
only the CH6a/b rework references belonged to 1aa77099. Each historical
row restored to its true SHA.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:11:27 +02:00
Erik
26b057bed8 docs: CH6a/b rework ledger SHA (1aa77099)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:10:52 +02:00
Erik
1aa7709988 fix(chat): CH6a/b rework — grip media, retail window-id model, floaty fixture
Applies docs/research/2026-08-10-ch6ab-review-findings.md in full:

- BLOCKER 1: UiResizeGrip now carries its ElementInfo/resolve pair and
  draws its own authored DirectState media (a synthetic parameterless
  grip still draws nothing, preserving existing resize-drag tests).
  DatWidgetFactory.BuildResizeGrip threads resolve through. All seven
  live grips on the main chat window now resolve a non-zero sprite,
  restoring the visible borders/corners CH6a silently dropped.

- SHOULD-FIX 2: ChatWindowState gains BroadcastTargetWindow, a sentinel
  distinct from every real window id (0-4), fixing the bug where the
  main window's explicit-addressing branch coincided with the broadcast
  check (both were literal 0). SetFilter's main-window no-op is dropped
  — the main window's filter is now genuinely settable. ChatWindowController
  .Bind takes a ChatWindowState (the same canonical instance the floating
  windows already share) and GetTranscriptLines builds a real accept
  predicate instead of accept:null. Verified safe: ClientLocal (0x1A)
  never reaches ChatLog (AddText routes it to the SpewBox and returns),
  so nothing observable regresses.

- SHOULD-FIX 3: UiButton.SuppressSelfToggle stops the four chat-window
  indicator buttons (DAT property 0x0B=true, no retail click handler)
  from flipping their own Selected mirror on a stray click.

- SHOULD-FIX 4: generated and committed chat_floaty_2100005b.json from
  the real installed dats; added the permanent RetailLayoutFixtureGenerator
  entry. All three flagged FloatingChatWindowController assumptions
  (input field, title bar, close button) are confirmed correct against
  real data — no controller code changes needed. New finding: unlike the
  main window, ALL EIGHT floaty border/corner elements are live Type-9
  grips (the floaty's own title bar is its move handle), so a floaty
  window resizes from every edge and corner.

- SHOULD-FIX 5: register row AP-189 documents the shared-500-entry/
  200-line-tail vs retail's per-window 10,000-line scrollback depth gap.

- NITs 1-5: documented the filter-persistence-only-on-/saveautoui
  asymmetry and the reconnect-preserves-filters intent; corrected the
  research doc's modifier-mask mislabel and the "ONLY function" false
  superlative; moved WrapText off ChatWindowController onto
  ChatTranscriptRenderer, closing the circular dependency.

Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline
12,392/4/0 at 22020ef2; net +28 tests, zero regressions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:09:32 +02:00
Erik
56b84deeab docs: CH6b ledger — review REJECT, rework in progress
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:32:09 +02:00
Erik
22020ef2c4 feat(chat): Campaign CH slice CH6b — floating chat windows 1-4
Mounts retail's four floating chat windows as always-resident, born-hidden
children per gmGamePlayUI::SetupChildren @0x004E9EC0, all sharing LayoutDesc
0x2100005B (window ids 0x10000505/0x1000050E/0x1000050F/0x10000510). New
FloatingChatWindowController (AcDream.App/UI/Layout) binds each window's own
widget tree — built fresh per instance from one shared imported ElementInfo
— reusing ChatWindowController's word-wrap + retail color-carry algorithm via
the extracted ChatTranscriptRenderer instead of duplicating it. A floaty
window has no talk-focus menu (research doc §2.2), so its entry field always
sends on Say; the mismatch against retail's possible shared-channel behavior
is UNVERIFIED and filed as #369/AP-188.

Runtime owns the per-window filter/open state: ChatWindowState (new,
AcDream.Core.Chat) seeds retail's exact PostInit defaults per window
(window 1 0x0000101C Speech/Tell/DirectSend/Emote, window 2 0x00040C00
Social/SocialSend/Allegiance, window 3 0x00080000 Fellowship, window 4
0x78000000 Turbine General/Trade/LFG/Roleplay) and implements the full
ShouldDisplay(windowId, targetWindowId, logTextType) display predicate from
ChatInterface::RecvNotice_DisplayFinalStringInfo @0x004F4640. It lives on
RuntimeCommunicationState.ChatWindows so every host borrows the same
instance. The main window's filter (0xFBFFFFFF, "no user filter") never
actually gates anything because its own explicit-address branch already
covers every broadcast line — that's why UpdateFromPlayerModule early-returns
for window 0 in retail, ported here by construction rather than a special
case.

Keybind wiring: InputAction.ToggleFloatingChatWindow1..4 and their
KeyBindings.RetailDefaults() chords already existed since Phase K.1c
(unwired until now). The MetaKeys table confirms retail's default is Alt+1
through Alt+4 (index 3 = bit 0x00000004, cross-checked against the same
file's Alt+A/D strafe and Alt+Enter/Tab/F4 rows). Routes through
GameplayInputCommandController -> RetainedGameplayWindowCommands ->
RetailUiRuntime.ToggleFloatingChatWindow -> the generic UiHost.ToggleWindow,
whose visibility-change event is the single chokepoint that syncs
ChatWindowState.SetOpen and mirrors the main window's 1-4 indicator button
regardless of what changed a window's visibility (keybind, close button, or
a restored layout).

A direct decomp read of gmMainChatUI::ListenToElementMessage @0x004CDA80 —
the only function in the whole binary that branches on a click message —
settles what the research doc had left as a hedge: it handles exactly
0x1000046f (max/min) and the talk-focus menu's selection message, with NO
case for 0x10000522-0x10000525. The four indicator buttons are PURE
one-directional mirrors in retail; clicking them does nothing.
ChatWindowController.SetIndicatorOpen ports this with no OnClick at all.
Corrected research doc §1.4 accordingly.

Persistence is local-only (register row AP-187; the retail 0x1000008C
GameplayOptions wire remains deferred to CH6f): window geometry and
open/visible state ride the existing generic RetailWindowLayoutPersistence
path for free once each window registers under its own WindowNames entry;
the four filter masks get a dedicated ChatSettings round-trip
(ChatWindow1Filter..ChatWindow4Filter, defaulting to the retail PostInit
constants) loaded at mount and saved alongside SaveLayout().

Tests: ChatWindowStateTests (defaults, TypeIsActive, the full display-rule
matrix, toggle/reset, revision counter), FloatingChatWindowControllerTests
(bind smoke tests against a synthetic 0x2100005B tree, per-window filter
routing, filter-change cache invalidation, fixed-Say submit), new
ChatWindowController.SetIndicatorOpen tests (Highlight/Normal state,
cross-window isolation, range validation), GameplayInputCommandController
routing for the four toggle actions, and a SettingsStore filter round-trip.
Full Release suite: 12,392 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:10:20 +02:00
Erik
98de4f5ab3 fix(chat): Campaign CH round 3 — SpewBox flush-top/font, /help exact print sequence
User-gate round 3 findings (a)-(c):

(a) SpewBox: TopOffset moves from the round-1 60px placeholder to 0 (flush
to the viewport top). SpewBoxController never wired DatFont/Font at all
before this round, so it silently rendered through the 15px debug
BitmapFont fallback; it now resolves retail dat Font 0x40000025
(MaxCharHeight=11px) through a new RetailUiRuntime.Assets accessor —
the smallest font id confirmed in use by any currently-imported retail
LayoutDesc fixture, cross-referenced against every
tests/AcDream.App.Tests/UI/Layout/fixtures/*.json dump and confirmed
against the installed DAT via AcDream.Cli dump-font-atlas. It is also the
chat window's own smallest font (the 0x2100006F floating-window 1/2/3/4
indicator badges), so both selection criteria the brief offered agree.
Both remain best-available approximations, not resolved retail values —
register row AP-178 updated accordingly.

(b)/(c) /help and /help death: round 2 extracted the individual retail
strings byte-exact but never traced ClientCommunicationSystem::DoHelp's
complete print sequence. Byte-swept DoHelp's own range plus the five
Summary-branch functions it calls into (HelpEmote/HelpSquelch/
HelpStatusGroup/HelpTextGroup/HelpAllGroup) against the PDB-paired
acclient.exe. Retail's real shape: bare /help prints exactly TWO scroll
entries (HelpPrefixNote, then the 13-item AvailableHelpListing built from
DoHelp's own literals and each group's Summary_HelpType branch, in exact
source order) — not the acdream-invented cheat sheet BuildHelpText()
built before. Any resolved /help <verb> gets the SAME two-entry shape:
HelpPrefixNote, then ForMoreInformationPrefix concatenated directly onto
the verb's own Detail text (retail's own unsubstituted "<command>"
literal, ported verbatim). ChatCommandRouter.EmitVerbHelp applies this
uniformly to every resolved verb, not just death. An unresolved verb now
shows retail's real "Unknown command" fallback text; that fallback types
0x1A (ClientLocal), which retail routes to the SpewBox exclusively — a
gap ChatVM's UI.Abstractions layer can't yet reach, filed as ISSUES #367
/ register AP-186 rather than left silently unregistered.

Jump-in-air (round 2's open item 1) was root-caused and fixed separately
at a5a7eb4f between rounds — recorded in the campaign ledger.

Debug suite (all projects): 12,329 passed / 4 skipped / 1 failed — the
one failure is issue #351, a pre-existing Debug-only streaming flake
confirmed reproducing identically on the pristine pre-round-3 commit via
git stash, not a regression. Release verification covers every project
reachable without rebuilding AcDream.App: a live client process (PID
15064) held its own Release binaries locked for the session and was not
killed per project policy — AcDream.UI.Abstractions.Tests (867/867, the
layer both /help fixes live in) plus every other non-App-dependent
project, all 0 failed. AcDream.App/AcDream.App.Tests/AcDream.Core.Tests
(the SpewBox fix's layer) are green in Debug only this session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 10:40:19 +02:00
Erik
1fd515436c feat(chat): Campaign CH slice CH6a — retail chat-window layout + 8-grip resize
Swap ChatWindowController's imported main-chat LayoutDesc from the wrong
0x21000006 (an unrelated layout whose root and 800px resize bar appear
nowhere in the EoR gameplay UI) to retail's ACTUAL main chat window,
0x2100006F (window root 0x10000600, authored 410x100 — confirmed by a
direct DAT dump, found in dats.Local not dats.Portal). Every downstream
compensation that existed only to paper over the wrong import is deleted:
the hand-cropped 490px content width, the dropped 800px resize bar, the
9px transcript patch, the orphan-sibling pruning, the max/min-vs-scrollbar
overlap shift, and the scrollbar top-reclaim. The window now mounts with
RetailWindowChrome.Imported (0x2100006F's own 8 border/corner elements are
its complete chrome) instead of the universal nine-slice wrapper.

LayoutImporter/DatWidgetFactory gain a Type-9 (UIElement_Resizebar) case:
UiResizeGrip decodes retail's exact four-bool BorderLocation algorithm
(0x2A=bottom/0x2B=left/0x2C=right/0x2D=top,
UIElement_Resizebar::StartMouseResizing @0x0046B7E0) into a ResizeEdges
bitmask. A direct DAT dump established the true shape: only 7 of the 8
grip-position ids are Type 9 — the straight top-EDGE strip (0x1000069C) is
a Type-2 Dragbar (move handle), not a Resizebar, because the main window
has no title bar. UiRoot now gives a directly-hit grip's own edges
priority over its generic proximity heuristic, and a directly-hit move
handle the same priority over ambient proximity — so the plain top strip
moves the window while its two corner grips resize it including the Y
axis, and all 4 edges + 4 corners work everywhere else. This also fixes
the reported "no diagonal cursor at corners" (CursorFeedbackController's
existing RetailCursorCatalog cursor ids already matched the DAT exactly;
they just never received a genuine diagonal edge combination) and "cannot
grow in Y from the bottom-right corner" (the old NineSlice+crop mount's
indirection is gone; the Imported mount uses the DAT's real
minH=100/maxH=2000/minW=300/maxW=2000 directly).

The 8 cosmetic "_Locked" border-art twins default hidden (register row
AP-185 — retail's UiLocked-driven art swap between the two skins is not
ported; UiRoot.UiLocked continues to gate the underlying interaction
correctly either way). The 4 chat-window-1..4 indicator buttons import
generically (visible, inert) for CH6b to wire. The two hand-drawn
translucent-black tints on the transcript/input are removed now that
their parent panels draw their own authored background sprites.

Filed #366 (chat window's new-unseen-text indicator 0x1000048C is
swallowed by UiText.ConsumesDatChildren, pre-existing and out of scope).
Corrected the research doc's "all eight grips" claim against the direct
DAT dump. Full Release suite: 12,317 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 09:38:27 +02:00
Erik
c1f1582576 fix(chat): Campaign CH user-gate round 2 -- portal notice rerouted to SpewBox, verbatim /help extraction, jump-in-air evidence
Item 2: retail's portal-space "In Portal Space..." notice is the SpewBox
(ECM_UI::SendNotice_DisplayStringInfo(0x1A,...) -> AddTextToScroll(str,
0x1A, 1, 0), hardcoded to the SpewBox per the decomp), not a dedicated
centered overlay. PortalWaitNoticeController and its lease are deleted;
PortalTunnelPresentation's per-rotation-segment cadence now writes
straight into RuntimeCommunicationState.AddText(ClientLocal) -- the
SpewBox's own dedupe-at-index-0 handles the repetition exactly as
retail's does. Register row AP-184 records the surface fix and the AP-178
scope extension.

Items 4+5: /help text was partially fabricated -- the user caught the
"/help death" meta-message. Generalized
tools/pdb-extract/sweep_weenie_strings.py to decode narrow
PStringBase<char> literals (the ClientCommunicationSystem::Help* family's
shape) alongside its original UTF-16LE support, then swept every
HelpXxxGroup function's exact byte extent against the PDB-paired
acclient.exe. 4 of 7 group topics (death/status/text/allegiances) are now
complete verbatim listings; the other 3 (channels/chatting/commands) keep
an honest UNVERIFIED note citing HelpStupidChannelHack @0x0056f290 (a
genuinely undecodable BN-mislabeled-fragment mechanism) instead of the
old fabricated sentinel. 7 of ~35 channel one-liners are also now
verbatim. ISSUES.md #364 tracks the remainder;
RetailCommandHelpTableTests.cs pins every result byte-exact.

Item 1: jump-in-air refusal still silent live is NOT reproduced and NOT
speculatively fixed. Exhaustive static re-audit found the mechanism
correct by construction (single-writer OnWalkable, exactly-once-per-frame
Update()/Capture(), no interfering edge-history resets). A live headless
repro (new jump-probe bot policy, real ACE connect) was blocked --
probeaccount2 has no character, and the graphical client already owned
testaccount this session so the task's own fallback rule forbade using
it. Two temporary probes are left behind ACDREAM_PROBE_JUMP=1 (blocked
entirely in Headless by the existing multi-session static-state guard --
graphical-only for the next round).

Item 3 confirmed fixed, no regression. Item 6 (resize: no diagonal
cursors, cannot grow Y from bottom-right) folded into CH6a's existing
scope.

Full Release suite: 12,267 passed / 4 skipped / 0 failed (up from
12,221/4/0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 08:40:24 +02:00
Erik
81f53bfd46 docs: CH6 research landed — chat-window shell plan (CH6a/b/c)
Wrong-LayoutDesc root cause (0x21000006 vs retail 0x2100006F), resident
floating windows toggled by keybind, 8 authored resize grips, global
opacity options 0x10000080/81.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 07:48:38 +02:00
Erik
47e40900f3 fix(chat): Campaign CH user-gate round 1 — jump-in-air edge, portal cue cadence, wrap/prefix/color fixes
The user tested Campaign CH's CODE-COMPLETE build live and reported ten
defects (docs/plans/2026-08-09-chat-parity-campaign.md, "User gate —
round 1"). Items A-G are fixed here; the remaining three (extra chat
windows on 1/2/3/4, resize working in only one corner, transparency/
artifacts) are out of scope for a fix and filed as slice CH6.

A. Jump-in-air refusal never fired live: the jump block only ever
   evaluated input.Jump inside the grounded-charge or already-charging
   branches. PlayerMovementController now detects the press RISING EDGE
   while airborne and reports WeenieError.NotGrounded once per press,
   leaving the grounded charge/fire path untouched.
B. ChatVM's invented "[System] " prefix is dropped — retail prints
   system text bare. [Popup] is unchanged (AP-175).
C. SpewBoxController's color is now the user-pinned exact value
   (1, 1, 0.247, 1), the same bright yellow as an incoming Tell.
   Register row AP-178 updated: color CLOSES, size/position/font stay
   open per the user's live report that they still differ.
D. Closes #329: PortalTunnelPresentation now emits the portal wait cue
   unconditionally on every rotation-segment boundary, matching
   gmSmartBoxUI::UseTime's decompiled else-arm exactly instead of gating
   on a 5-second hold local transits never reached. PortalWaitNotice
   Controller now renders it in the same pinned yellow as item C.
   Register row AP-150 retired.
E. Closes #362: new ClientCommandResponses.cs parses and renders the
   four previously-unhandled inbound GameEvents (ChannelIndex,
   ChannelList, AvailableHouses, AllegianceInfoResponse), each ported
   line-for-line from the named-retail decomp's inbound handlers.
   Register row TS-70 retired.
F. ChatWindowController.WrapText now splits on embedded '\n'/'\r\n'
   first, then word-wraps each segment independently — server text like
   /help's reply no longer collapses onto one line.
G. The chat input field's right edge no longer holds a fixed absolute
   pixel position across a window resize; Bind now upgrades it to
   retail edge-mode 1 (UiLayoutPolicy) or the AnchorEdges.Right stretch
   fallback so it tracks the window's client width instead of
   overflowing past a narrower resize.

Full Release suite: 12,247 passed / 4 skipped / 0 failed (baseline
12,221/4/0 + 26 new tests across items A, E, F, G).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 23:42:32 +02:00
Erik
38f0c0defc docs: Campaign CH closeout sweep — CODE-COMPLETE pending user gate
CH5: flip the campaign plan's status header from ACTIVE to CODE-COMPLETE,
correct the CH4 ledger row's suite count to its final 12,221 (was showing
the pre-review-fix 12,190) and fill in the CH5 row, and add a closeout
paragraph for the previously-undocumented 5d247d55 re-review round.
Register sweep found one drift: the TS section header claimed 42 active
rows against an actual recount of 40 (TS-66 is retired/struck-through and
was miscounted as active) — corrected. AP/AD/IA/UN section counts,
AP-175..183, AP-176 retirement, and UN-9's deletion all verified
consistent, no other changes. Cross-referenced issues #359-#363 to the
campaign doc. Extended CLAUDE.md's Current-state Campaign-status sentence
to record Campaign CH's CODE-COMPLETE status and carried tail. Added a
Campaign CH entry (plus a missing Campaign A entry) to the roadmap's
shipped-campaign summary block, matching the Campaign V/N/P paragraph
format.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 22:34:54 +02:00
Erik
813cc51f9a docs: CH4 closed at 5d247d55; Campaign CH user test script
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 22:26:15 +02:00
Erik
5d247d5518 fix(chat): CH4 re-review fixes — dialog-queue reentrancy, settings option-bit chokepoint
Should-fix 1: RetailDialogFactory.CloseDialog's queued branch removed the
active DialogInfo, ran DialogDone (whose callback can synchronously open a
new dialog under the SAME queue key — the two-stage house-abandon
confirmation does exactly this), then called OpenNextDialog, which did an
unconditional Dictionary.Add on a key the reentrant dialog had already
re-occupied. Retail's HashTable::add tolerates the duplicate; Dictionary
throws. OpenNextDialog now returns early when the queue key is already
active — the reentrant dialog's own eventual close drains the queue.

Should-fix 2: @join/@leave wrote the local RuntimeCharacterOptionsState bit
before sending, but the Settings Chat toggles reached a second binding
(SendSingleCharacterOption) that only sent the wire message, leaving the
Turbine membership gate stale until the next PlayerDescription.
LiveSessionRuntimeFactory.CreateCommandBindings now has one shared local
function for both entrances.

Should-fix 3: corrected TS-68/#360 wording again — retail's DoAllegiance
dispatcher table EXECUTES boot/ban/officer/title/motd/name/lock/house/
chat/broadcast locally through their own handlers; acdream shows the
unrecognized-subcommand refusal for all nine pending the #360 port. What
matches retail is the ownership rule (the verb never reaches
DoChannelCommand/the server), not the subcommand behavior itself. Removed
the inaccurate "matching retail, not merely harmless" / "now matches
this" claims from both the register row and the issue.

Nits: corrected the HouseAbandonDialogCallback_First citation (0x00580E1A
is DoHouse's load site for the callback pointer, not the function entry —
the entry is 0x00580240, with the stage-2 confirmation string built at
0x005802D8) in both ClientCommandController.cs and the mirrored test
comment; added an InlineData case pinning "@clist allegiance" to
RequestChannelList(0x02000000); converted RetailClientCommandCatalog.
KnownVerbs from a plain array to a FrozenSet<string> with
StringComparer.OrdinalIgnoreCase, matching the file's other lookup tables.

Suite: 12,221 passed / 4 skipped / 0 failed (Release), up from CH4's
12,216/4/0 — net +5 tests, no removals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 22:24:43 +02:00
Erik
59c053ee47 docs: CH4 review-fix ledger SHA (724ef2d3)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 22:00:44 +02:00
Erik
724ef2d389 fix(chat): CH4 review fixes — allegiance ownership guard, house-abandon confirmation
Blocker 1: an unrecognized "@allegiance <sub>" subcommand escaped
TryMatchAllegiance (which only claimed "info"/"hometown") and fell through
the unregistered-tag channel fallback, broadcasting the raw subcommand
text to the Allegiance chat channel (0x02000000). Retail's own
DoAllegiance never reaches DoChannelCommand for an unrecognized
subcommand — it claims the whole verb and prints its own client-local
refusal. TryMatchAllegiance now claims "allegiance"/"all" unconditionally
and shows retail's "Please see @help Allegiance..." text; ChatCommandRouter
also gained a blanket RetailClientCommandCatalog.KnownVerbs ownership
guard in TryDispatchChannelFallback as defense in depth.

Blocker 2: "@house abandon" sent 0x021F immediately with no confirmation.
Retail runs a real two-stage dialog before Event_AbandonHouse(); ported
both verbatim strings and chained two ShowConfirmation calls.

Should-fixes: a bare unregistered tag with no text now passes through
silently instead of showing a refusal that belongs to a different retail
function; @join/@leave update RuntimeCharacterOptionsState locally (new
SetOptionBit) before the wire push so the Turbine membership gate stops
refusing a just-joined room; @permit accepts multi-word names; @clist/
@on/@off validate shape only and raise WeenieError 0x422 for an unknown
tag; @mr/@pr help text is now the verbatim retail strings; corrected
issue #360, register row TS-68, the campaign doc's B.7 note, and a stale
RetailChannelTagTable comment; filed issue #363 + register row AP-183 for
the deferred error-typing debt.

Nits: fixed TryMatchHouse's stale doc comment, the AP-182/@title "stores
the value" comments (the binding is a no-op), IsUnregisteredFallbackTag's
olthoi false-positive, added /g and /rp binding-level conformance pins,
made @index ignore extra arguments, and noted the six removed invented
verbs in ISSUES.md.

Suite: 12,216 passed / 4 skipped / 0 failed (Release), up from CH4's
12,190/4/0 — net +26 tests, no removals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:59:35 +02:00
Erik
090825e703 feat(chat): Campaign CH slice CH4 — command registry completion
Brings acdream's / and @ command parsing to parity with the complete
retail registry (130 registered verbs + 22 unregistered GetChannelID
fallback tags = 152 client-parsed verbs), per
docs/research/2026-08-09-chat-retail-command-registry.md.

Parser semantics (retail OnChatCommand/DoCommand):
- : and ; rewrite to "@emote <rest>" before dispatch.
- Verb trailing-comma trim ("@f, hi" == "@f hi") applied at every
  verb-lookup site in the catalog and the parser.
- @tell/aliases split the target on the FIRST COMMA, not the first
  whitespace token, so multi-word names work ("@tell Aunt Agatha, hi").
- The 22 unregistered GM/faction channel tags (admin, sentinel,
  celestialhand, ...) now broadcast for real via a new
  RetailChannelTagTable + SendRawChannelCmd bypass, reusing the existing
  BuildChatChannel wire builder.

Binding corrections:
- /g, /group, /party -> Fellowship (0x800), not General.
- /rp -> reply alias (retail's own help text confirms "@r or @rp"), not
  Roleplay; /role (an acdream invention) deleted.
- /allegiance, /all -> the allegiance management command
  (RetailClientCommandCatalog), not a channel verb.
- /house no longer swallows unrecognized subcommands with a local usage
  error; they now correctly fall through to ACE.
- @mr/@pr pinned as permanently non-executable (retail registers them
  with a null function pointer).

New verbs with real local execution: endurance, speaker, title (silent,
AP-182), chat, notell, join, leave, permit, hslist, index, clist, on,
off, alh/ah (+ "@allegiance hometown"/"ho"), "@allegiance info",
"@house abandon"; a missing-alias sweep across pkl/hou/message_types/
msgtypes/msg_types/rt/send/whisper/w/vassal/covassal/co-vassals/c/
fellows/group/party/guild/gu/cg/ct/clfg/crp/soc/o; the non-retail
inventions gen/cv/lookingforgroup/tr/role/h are deleted. New Core.Net
wire builders (IndexChannels, ListChannels, AddChannel, RemoveChannel,
RecallAllegianceHometown, AllegianceInfoRequest, ListAvailableHouses,
AddPlayerPermission, RemovePlayerPermission, AbandonHouse) are all
parameterless or single-field payloads cross-checked against ACE's
GameAction readers, not guessed.

Deferred (filed as #360/#361/#362, register rows TS-68/TS-69/TS-70):
the ~22 remaining allegiance/house subcommands + standalone @motd
(largest single item, needs its own slice per the doc), the three
still-inert pure-local commands (day/log/render), and the inbound
GameEvent responses for the new outbound requests. All correctly fall
through to ACE server-passthrough rather than being silently swallowed
or faking success.

RetailCommandRegistryConformanceTests pins the complete 152-verb
registry against production: every verb resolves through exactly one
production surface if Implemented, through none if HelpOnly/
ServerPassthrough, and two reverse-direction tests fail the build if
RetailClientCommandCatalog or ChatInputParser ever claims a verb
outside this registry again. Final tally: 138 Implemented / 5
ServerPassthrough / 9 HelpOnly = 152.

Release suite: 12,190 passed / 4 skipped / 0 failed (up from CH3's
11,964/4/0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:10:17 +02:00
Erik
9247d5d5b5 docs: CH3 review-fix ledger SHA + suite count (e07fba57, 11,964)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 20:25:48 +02:00