11 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a9b6435f55 |
fix #385: Options dropdowns — white centered text + size-to-content popup
User gate report (Campaign OP happy-testing round, 2026-08-13): every Config-tab dropdown drew its text gold + left-aligned and its popup a fixed 6 rows regardless of item count. All three were unmeasured styling divergences — the authored data (new probe menuprobe3, live DAT) says: - button label child 0x10000355: fontColor white, hJustify=Center - row template 0x1000035A: fontColor white, hJustify=Center - popup ListBox 0x10000358: edge-docked L=T=R=B=1, the authored condition arming retail UIElement_Menu::RecalculatePopupSize @0x0046caf0 — popup resizes to the ListBox's summed content height, uncapped (0x0046e5f4..0046e66c via ResizeScrollableArea's 0x32 broadcast) UiMenu gains three opt-in properties (ButtonTextCentered, ItemTextCentered, PopupSizeToContent) plus retail Open @0x0046cc42's empty-list gate; chat + vendor keep the class defaults, so their shipped behavior is untouched. ConfigOptionsPageController.ApplyMenuChrome wires all four corrections for the 8 Config menus with the probe citation. The same probe found vendor's authored popup ListBox is ALSO docked while our vendor dropdown ships G5's fixed 6-row window — filed as #386 + register row AD-88 (UNCLEAR: the G5 retail screenshot and the decomp mechanism conflict) instead of silently reworking a user-gated surface. The "resolution change resizes the window" observation from the same report is #374's designed windowed-mode behavior (display-mode switching is #376/#377) — no change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
355c86a6f6 |
fix #374: open dropdown popups get first claim on pointer routing
Campaign OP gate 2 root cause: UiElement.HitTest walks siblings front-to-back by z-order, so an OPEN UiMenu's extended button+popup hit-test union was never consulted when a LATER sibling's rect overlapped the popup area — on the Config tab every dropdown has rows below it, so Resolution-item clicks toggled the Full Screen / VSync rows underneath (the gate session's persisted fullscreen/vsync flips were exactly those stolen clicks). Latent since UiMenu existed; vendor/chat menus only worked by z-order luck. Fix: UiMenu's open/close now registers with UiRoot (SetActivePopup / ClearActivePopup); a registered popup gets FIRST claim on mouse-down, scroll, and hover routing; a press outside a live popup dismisses it and is SWALLOWED (the dismissing click must not act on what sat underneath); hidden/detached owners self-heal the registration on the next pointer event. UiMenu gains the IsOpen seam and a single SetOpen writer. Also in this commit, from the same investigation: - SilkRuntimeDisplayWindowTarget.Apply documents the fullscreen half honestly: IViewProperties.VideoMode is READ-ONLY, so a resolution pick while fullscreen cannot switch the display mode through Silk's abstract API — split out as #376 (native glfwSetWindowMonitor port) rather than half-shipping untested native interop at a gate tail. - Gate script §OP6 step 8 re-scoped: test resolution in WINDOWED mode. Regressed by tests/AcDream.App.Tests/UI/UiMenuPopupRoutingTests.cs — 4 tests driving the real UiRoot input path on a mounted overlapping tree, with an in-test overlap CONTROL click so the popup assertions cannot pass vacuously (the #372 lesson: only mount+drive-input tests catch this class; every fixture-conformance test stayed green through this bug). Full Release suite: 13,081 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
472525b99e |
fix(ui): OP6 rework — six range captions, un-invert Sound enabled flags, five font faces
Fixes all three MUST-FIX findings from the OP6 REJECT review (docs/research/2026-08-11-op6-review.md) plus its SHOULD-FIXes and NOTEs. M1 — the "retail ships zero range captions" claim was a Binary Ninja constant-folding artifact (the same class the header-string globals a few lines above already worked around). The six SetSliderLabel call sites byte-decode to reads of runtime-filled ID_Graphics_Value_* globals, not immediate zeros (PE-byte-verified against the PDB-paired acclient.exe, independently re-derived in this session, not just re-asserted from the review). ConfigOptionsPageController.BuildSliderRow gained optional rangeLowKey/rangeHighKey parameters wired for all six idx6 sliders (Camera Stiffness Soft/Hard, Adjustment Speed Slow/Fast, FOV Narrow/Wide, Screen Brightness Dark/Bright, Graphics Performance Speed/Detail, Degrade Distance Close/Far) via the same SetRangeLabel mechanism OP5's Chat opacity sliders already established. Mouse Look Sensitivity (idx3) correctly stays uncaptioned — the one genuine SetSliderLabel omission. Class doc corrected; gate-script lines 535/653-equivalent corrected in place. M2 — the three Sound "Disabled" toggles were semantically inverted: SoundManager::effect_sounds_enabled/ambient_sounds_enabled/ interface_sounds_enabled are all compiled = 1 in .data, and UserPreferences::RegisterPreference binds the checkbox's boolean value DIRECTLY onto those enabled-sense statics — checked-by-default means enabled-by-default, not disabled. AudioSettings.SfxDisabled/AmbientDisabled/ InterfaceDisabled renamed to SfxEnabled/AmbientEnabled/InterfaceEnabled (fresh JSON keys — the rejected slice's keys never shipped in an accepted build); RuntimeSettingsStartupTargets.ApplyAudio now computes effective volume through the extracted, independently-unit-tested pure function ComputeEffectiveCategoryVolumes (enabled ? slider : 0f). This closes the blast radius the review flagged: a missing key in an EXISTING settings.json now falls back to AudioSettings.Default, which is enabled=true, so a fresh launch is audible, not muted. AP-199's wording and gate-script step 6 corrected; the enshrined-inversion test rewritten to assert the correct default and a new SettingsStore test pins the legacy-file fallback path. M3 — UI_ChatFontFace now ships all five of retail's authored choices (Arial, CourierNew, PalatinoLinotype, Tahoma, TimesNewRoman — a fixed compile-time array at gmClient::InitUIPreferences, PE-byte-verified present verbatim in .rdata, not a per-machine runtime enumeration as the rejected slice's comment claimed). Default index 2 (PalatinoLinotype) now indexes a real entry. S1 — Bind() now emits the sixth trailing AddSeperator retail's own InitOptions ends with (0x0049E80D), matching retail's 39-item ListBox (6 headers + 6 separators + 27 option-widget-rows) instead of 38. S2 — Screen Brightness gets its own DisplaySettings.ScreenBrightness field ([-1,1], default 0) instead of overloading Gamma, which has a different unit system (default 1.0, legacy [0.5,2.0] slider) and its own live Settings-panel consumer. S3 — UiScrollbar and UiMenu gained a settable TooltipText surfaced through GetTooltipText (UiButton's existing pattern). Every slider and menu row's own interactive widget (not just toggle/trio rows) now carries retail's "<label>_Help" tooltip, verified as a universal suffix convention across every AttachPreference site touched by this tab. S4 — "800x600" added to DisplaySettings.AvailableResolutions: a genuine retail display mode (Device::ForceDisplayResolution(1,0x320,0x258) at startup) and the Config tab's own byte-verified Resolution row default, not an invented preset. Defaults now lands on a highlighted, re-selectable dropdown entry instead of an orphaned value. S5 — four new/extended tests: ComputeEffectiveCategoryVolumes gets a dedicated pure-function value assertion (Theory + a default-profile-is- audible Fact) in RuntimeSettingsControllerTests, closing the "only event order was asserted" gap that let M2 ship; a label/choice-key conformance table in ConfigOptionsPageControllerTests enumerates every key this tab queries (traced directly from the fixed code paths, not guessed) and fails on an invented OR a dropped key; a per-row DefaultValue pin asserts every row's default against the retail literal directly, independent of the underlying settings-record defaults; and the S1 separator fix gets its own 39-item stacked-ListBox count pin. NOTEs — AP-198's row count was always ten (its own enumeration never said nine); the commit-message inconsistency N1 flagged is reconciled in both the row and the section-summary line, and its Screen Brightness sub-clause now matches S2. N2: Bind() now reads the scrollbar id from UiTemplateListBox.ScrollbarElementId (dat property 0x72) instead of a hardcoded constant. N3 (batch Defaults writes) and N4 (AfterApply on Config-tab entry, needs no action) are left as recorded — out of this rework's scope per the review's own disposition. Full Release suite: 13,125 passed / 4 skipped / 0 failed (baseline 13,117/4/0 — net +8 tests added, 0 regressions, 0 removed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
33b45ee581 |
fix(ui): vendor dropdown polish — authored arrow-cap with open/closed flip, downward popup, left-aligned rows
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Three gate findings, each settled by authored data rather than invention: the button face is retail's two-piece assembly and the 17x19 arrow-cap 0x1000034E now renders with its authored Normal(closed)/Highlight(open) states; the popup direction is an AUTHORED attribute (UIElement_Menu::Open pc:120210-120252 — bool attr 5, chat authors upward=true, the vendor menu authors nothing and defaults downward), so both menus are now byte-faithful with no special case; and the 19/20px text indents were chat-specific checkbox/LED clearances the vendor rows don't author — measured against the live retail font, "Spell Components" overflowed by 11px and now fits with 8px to spare. Chat's menu defaults are bit-identical and its tests untouched. AP-161's arrow-cap note closes. #351 files the pre-existing FarLoad Debug flake (three sightings today, never in clean-room Release). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
5224e43890 |
fix(vendor): gate-findings pass — the X button HIDES like retail, clicks return, the dropdown scrolls, pyreal suffix, staged-tab slots
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The user's connected gate found five issues; each fixed at the root: G4 (the discovery): retail's vendor X button calls only SetVisible(0) (pc:204147-204182) — the SESSION stays open and re-using the vendor lands on the same-session refresh; the range watcher remains the sole real close. Our port invented a full teardown on X, which is exactly why reopening died. The Runtime fixture proves the wire dispatch was never the problem; ACE has no already-open short-circuit. G3 (regression from the drag-suppression fix): denying IsDragSource also dropped press capture, so clicks fell through to window-drag. UiItemSlot.HandlesClick now claims presses for any occupied cell independent of drag eligibility — clickable and draggable are separate concerns. G5: the authored popup 0x21000043 is ONE scrollable column with a real scrollbar subtree (live-dat scan: ListBox 0x10000350 + scrollbar 0x10000351), not a 3x6 grid. UiMenu gains an authored-driven Scrollable mode (wheel, thumb drag, track paging, up/down buttons); chat's menu is untouched and its ten tests prove it. G1: retail's cost format is "%s %hsp (you have %hsp)" — the p after each %hs is a LITERAL pyreal suffix the port swallowed as part of the specifier. Restored. G2: the Buying/Selling pages' authored lists (same cell template as Items) get the empty-slot fill, presentation-only until staging. Clean-room complete solution: 11,390 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
9aaf97e785 |
Revert "Campaign V slice V4a" - it lost world multisampling
This reverts
|
||
|
|
ceec3bc440 |
feat(render): Campaign V slice V4a - port TextRenderer/BitmapFont/DebugLineRenderer/TextureCache onto IGpuDevice
TextRenderer, BitmapFont, DebugLineRenderer, and TextureCache's UI-texture
upload path (GetOrUploadRenderSurface/UploadRgba8) now issue every draw and
resource creation through the pinned IGpuDevice/IGpuFrame/IGpuPassEncoder
RHI contract instead of raw GL. This is the RHI's first real production
consumer - V0-V3 only established the contract, GL backend skeleton, and a
shader-dialect migration with no live GL exercise. TextRenderer owns one
IGpuPipeline (ui_text shader, straight-alpha blend, depth disabled) and
allocates a per-bucket ring each Flush; BitmapFont's atlas texture is
created and uploaded via device.CreateTexture/.Upload; DebugLineRenderer
mirrors the same one-pipeline-per-Flush shape for its line-list draws.
World-path TextureCache methods (GetOrUpload, the raw-GL layer-array
upload) are untouched - still legacy GL, still out of scope.
Frame lifecycle: GpuDeviceFrameLifetime (RenderFrameOrchestrator.cs) wraps
IGpuDevice.BeginFrame()/IGpuFrame.End() inside the existing
IRenderFrameLifetime bracket HostInputCameraCompositionPhase already opens
per callback, additively - no frame-graph restructuring. Ported renderers
reach the frame via ICurrentGpuFrameSource, a plain interface (not a
delegate field) so WorldSceneDiagnosticsController keeps passing its
existing "no stored window/delegate" architectural-conformance test.
Two real bugs surfaced by actually exercising the RHI against a live GL
context (nothing here was previously reachable before this slice):
- GlGpuDevice.BeginFrame() now resets the render-state cache every frame.
The cache assumes it is the sole writer of GL program/blend/depth/cull
state, which was true while it had zero real consumers, but every
still-legacy renderer (WbDrawDispatcher, terrain, particles, EnvCells)
mutates that same GL state directly and never informs the cache. Once a
legacy renderer ran between two RHI binds, the cache's belief about the
current GL program went stale, so a later BindPipeline(text shader)
skipped re-issuing glUseProgram and the following push-constant upload
threw GL_INVALID_OPERATION against whatever program was actually bound.
Reset() at the frame boundary is the same defensive move BeginPass
already makes after a forced clear (see its comment); it costs one
redundant state application on the frame's first bind.
- GL_MULTISAMPLE has no representation in the pinned contract. Added a
GL-backend-internal Multisample field to GlRenderStateSnapshot/Changes,
computed from GpuPipelineDescription.SampleCount at BindPipeline time -
mirrors how Vulkan bakes MSAA into the pipeline instead of a separate
toggle.
Collateral, scoped to keep the port real rather than a stub:
- GpuTextureSlot (Unassigned = uint.MaxValue, NOT 0) now flows through
every consumer of TextureCache.GetOrUploadRenderSurface/UploadRgba8 and
TextRenderer.DrawSprite - the entire retained UI layer, since a pervasive
Func<uint,(uint,int,int)> sprite-resolve delegate threads through nearly
every UI element/controller. Every prior `== 0` / `!= 0` "no texture"
check became `.IsAssigned` / `!.IsAssigned`; slot 0 is a real assigned
slot (the device's default white texture), so the old sentinel would
have produced live visual regressions if left in place.
- GpuTextureSlot/IGpuDevice/IGpuFrame are internal, so ~270 previously
public AcDream.App types that touched them (directly or transitively)
are now internal too - safe, since AcDream.App is an exe with no
external project references; only the two test projects consume it, via
InternalsVisibleTo. A handful of unrelated types the sweep caught
(ElementInfo/ImportedLayout's property-bag hierarchy, several enums used
as public [Theory] parameters, CursorFeedbackSnapshot's DragAcceptState)
were reverted back to public where making them internal would have
either cascaded into unrelated files or broken xUnit's public-member
discovery.
- ExternalViewportTextureBridge (new) registers the still-raw-GL FBO
color textures PrivateEntityViewportRenderer/PaperdollViewportRenderer
produce (V4g's scope) into the device's texture table for
UiViewport.TextureHandle, via a temporary
GlGpuDevice.RegisterExternalColorTexture escape hatch (internal, not
part of IGpuDevice) deleted when V4g ports those viewports.
- TextRenderGlStateScope.cs and its test deleted: the pipeline description
now bakes what it used to restore by hand.
- ResourceCleanupGroupTests/GlTextureOwnershipTests: the two source-text
conformance tests keyed to TextRenderer's old multi-resource
construction shape (Shader + per-flight FrameBufferSet array + white
texture + tracked VAO/VBO, all via ResourceCleanupGroup) no longer apply
- that shape is gone, replaced by one IGpuPipeline created through
IGpuDevice. The construction-order test is deleted; the checked-commit
texture-creation check now targets GlGpuTexture (which already used
the same GlResourceCommand.CreateName primitive before this slice).
Gates:
- dotnet build -c Release: 0 warnings, 0 errors (AcDream.App has
TreatWarningsAsErrors).
- dotnet test tests/AcDream.App.Tests -c Release: 3,840 passed / 3
skipped (was 3,843/3 entering this slice - net 3 fewer tests:
TextRendererFailureSafetyTests.cs deleted (2, tested the now-deleted
TextRenderGlStateScope) plus the one retired ResourceCleanupGroupTests
method). Full solution: 8,908 passed / 5 skipped across all nine test
projects.
- Offline pixel gate (tools/run-offline-pixel-gate.ps1, parent
|
||
|
|
d7002552bc |
fix(D.2b): behavioral widgets are leaf — ConsumesDatChildren (chat menu open)
The generalized channel menu wouldn't open: the factory recursed the Type-6 menu element's dat children, building its invisible Type-12 label child as a UiText. Hit-testing is children-first and UiText consumes MouseDown (selection), so the label child swallowed the menu button click and the dropdown never opened. The transcript similarly gained an invisible Ghosted-button child (a 16x16 selection dead-zone). The old hand-made build never had these — it skipped Type 12 and hand-placed the widgets with no children. Fix: behavioral widgets (Meter/Menu/Button/Scrollbar/Text/Field) draw their full appearance and reproduce their dat sub-elements procedurally, so they are LEAF — the importer must not build their dat children as separate (click-stealing) widgets. Add UiElement.ConsumesDatChildren (default false; the 6 behavioral widgets override true) and gate LayoutImporter recursion on it (replacing the UiMeter-only special case). Only generic containers (UiDatElement, panels) recurse. Visually confirmed in the live client (channel menu opens; General/Trade selected and sent). Vitals unchanged (UiMeter was already leaf). Full suite: 404 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
67e5b8cff2 |
fix(D.2b): UiMenu — controller owns Selected (review fix for Task 4)
Review caught a behavior divergence: the generic UiMenu auto-set its own Selected on any enabled pick, while the controller's EnabledProvider keeps the null-payload specials (Squelch / Tell-to-Selected) enabled/white like retail. So a special-item click set Selected=null and shifted the highlight onto the deferred placeholders — and the menu tests masked it by using a different (specials-disabled) gate than the controller ships. Fix: clean MVC contract mirroring retail UIElement_Menu::NewSelection — the widget REPORTS the pick via OnSelect; the controller OWNS Selected (it sets it only for talk-channel payloads). A special-item click now fires OnSelect(null), the controller ignores it, and the active channel + highlight stay put — observably identical to the pre-generalization widget, and extensible for when Squelch lands. Tests realigned to the controller's gate (specials white) and to the controller-owns-Selected contract. Full suite: 403 passed, 2 skipped, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
955f7a69a8 |
feat(D.2b): UiMenu (Type 6) — generic dropdown; channel knowledge moves to controller (widget-generalization Task 4)
UiChannelMenu → UiMenu: removed ChatChannelKind, the 14-item array, the button-text map, and the availability default. Generic surface: MenuItem (label + object? Payload), Selected (object?), OnSelect, EnabledProvider, ButtonLabelProvider, RowsPerColumn/RowHeight/ColumnWidth (all settable). All draw/event mechanics unchanged — same popup geometry, same click coordinates, same 8-piece bevel, same 3-slice button face. ChatWindowController gains ChannelItems[], ChannelButtonLabel(), and ChannelAvailable() (verbatim from old widget), and populates the factory-built Type-6 UiMenu via find-by-id rather than constructing a replacement widget. The Menu property type is now UiMenu. OnChannelChanged wrap replaced with the generic OnSelect wrap for the ReflowInputRow hook. DatWidgetFactory registers Type 6 → new UiMenu(). Tests: UiChannelMenuTests → UiMenuTests (10 tests, all green); factory Type6 test added; ChatWindowControllerTests updated to use OnSelect. Divergence register: AP-42 added (flat item model vs retail nested-submenu MakePopup @0x46d310 — latent, unreachable through the chat menu). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
Renamed from src/AcDream.App/UI/UiChannelMenu.cs (Browse further)