Retail authors ONE caption element per bar with per-mode state strings,
switched by a PassToChildren state cascade (gmPowerbarUI::
RecvNotice_BeginPowerbar @0x004DA730 sets 0x10000042 Jump / 0x10000043
Melee / 0x10000044 Missile / 0x10000045 DDD; installed-DAT probe
confirmed every string + PassToChildren flag).
- Jump bar (user gate): the floaty powerbar's caption child (0x10000035:
JumpMode 'Height', authored HJustify=Center over the bar) was dropped
by UiMeter's child absorption. The stateful-fill meter build now
absorbs it into per-state labels; TrySetRetailState latches the
caption and OnDraw shows it when no live Label provider is bound.
JumpPowerbarController's existing JumpMode flip now surfaces 'Height'
with zero controller changes. The mount gained the string resolver the
Build call never passed.
- Combat bar (user gate): label 0x10000052 authors 'MeleeCombat' ->
'Power' and 'MissileCombat' -> 'Accuracy'; the controller latched the
MELEE string once at bind. CombatUiLabels now resolves both authored
strings and OnCombatModeChanged sets the mode's string - switching
live when swapping melee <-> missile weapons in combat. Also fixed
the mode-state flip target: the states live on the BASIC PANEL
(0x1000005C, PassToChildren), not the layout root (Hide/ShowDetail
only) - the old _root flip was a silent no-op.
New env-gated ACDREAM_PROBE_POWERBAR layout probe (kept, house
pattern). App suite 4,987/3 skips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both OP4 reviews converged on one headline bug (Character-tab rows never
re-read live server truth after their pre-login constructor-word seed) plus
overlapping MUST-FIXes. All ten converged/consolidated findings land here:
MUST-FIX:
- BoolOptionRow.SaveCurrentValue now re-reads its live binding (retail's
GetValue()-into-SaveCurrentValue) on every OnShown — panel open, tab
switch in, initial activation — instead of trusting the pre-login
constructor word it was built with. Reset/tab-switch can now only
restore values that were actually live at the last show. LockUI's
host.Root.UiLocked one-shot mount seed now also converges on every
PlayerDescription via the existing OnCharacterOptionsChanged hook.
- Apply/Reset are wired to OptionPage.OnOptionChanged in production
(Ghosted when nothing changed, Normal when dirty, run once at bind so
both start disabled per retail's PostInit); Defaults stays ungated.
- The Combat panel's three LEDs (Repeat Attacks/Auto Target/Keep in View)
now read/write the same RuntimeCharacterOptionsState seam the Character
tab uses instead of a disconnected client-local GameplaySettings copy —
closes the "two writable copies" divergence. The three now-orphaned
GameplaySettings fields and RuntimeSettingsController's mirror
properties/SetCombatGameplay are deleted outright; the headless host's
hardcoded AutoRepeatAttack/AutoTarget now read the live option bit.
- RuntimeSettingsController.SetUiLocked's convergence guard now compares
against the last value actually applied to the runtime target instead
of the persisted GameplaySettings.LockUI snapshot, which could already
match a server-derived request without ever having been pushed.
SHOULD-FIX:
- DisplayTimeStamps now prefixes every chat producer (ChatLog.Append is
the one seam all of them funnel through), not just AddText's own
callers — heard speech, emotes, Turbine channels, and combat text were
previously missed. The prefix format escapes its colons and forces
InvariantCulture instead of the culture-dependent TimeSeparator
placeholder.
- sky.frag now honors uFogParams.w (fog mode) like the mesh/terrain
shaders, so Disable Distance Fog stops the sky dome's horizon band from
blending toward fog color too.
- Corrected the "byte-verified" overclaim on the timestamp format string
doc comment (BN-sourced, wire doc U6) and the AP-194 anchor-column
class-name typo; the RunAsDefaultMovement doc comments now cite retail's
actual acclient.h enumerator name.
- Added: DispatcherMovementInputSource's option x modifier truth table
(incl. || AutoRunActive with the option off), the per-page Apply/Reset
enable-gate tests, a real checkbox.OnClick/ToggleBehavior-driven click
test, and hash-pins for the six header string keys.
- Gate script step 8 corrected for the logout-flush false-failure
(closing the panel before relogging is load-bearing); a new step
documents the enable-gate sequence and the Combat-panel/Character-tab
cross-check.
Register: AP-196 (the Group-C default-source change + GameplaySettings
retirement) and AP-197 (the ignored per-character timestamp format
override) filed in this commit.
Full Release suite: 13,044 passed / 4 skipped / 0 failed (was 13,008/4/0;
net +36 tests from new coverage and legitimate assertion updates from the
GameplaySettings retirement).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts ceec3bc4. Two independent reasons, either sufficient.
The rendering regression. The slice deleted TextRenderGlStateScope, which
saved GL_MULTISAMPLE and GL_SAMPLE_ALPHA_TO_COVERAGE on entry, disabled them
for the text pass, and restored them on exit (TextRenderGlStateScope.cs:111-112
and 153-154 at the parent commit). Its replacement bakes that state into the
text pipeline but nothing restores it, and GlGpuPassEncoder.Dispose does not
either. Every world renderer is still raw GL at this point in the campaign, so
from the first UI frame onward the world drew with multisampling disabled.
The offline pixel gate caught it: 1,791 of 563,200 compared pixels differed,
0.318% against a 0.001 threshold. The commit message attributed this to
wall-clock-driven ambient animation shifting phase, and committed through the
failure. That explanation does not survive its own control: capturing twice at
the reverted-to commit differs by 19 pixels and twice at the slice's own commit
by 8, while base-versus-head differs by 1,791 - a 224x gap that no shared-noise
source explains. An amplified difference image settles it visually: the changed
pixels are the silhouette edges of every tree, building and rock, with terrain
interiors, water and the entire UI untouched. That is the signature of losing
edge antialiasing, not of animated sprites.
This is the exact failure mode two existing memory notes already warn about -
a mid-frame renderer must set every GL state it uses rather than inherit it,
and issue #52's lesson that a rendering migration must audit per-pass GL state
before declaring itself done.
The scope. The brief was three small leaf renderers plus additive frame-
lifecycle wiring, roughly ten files. The commit changed 334 files with 3,665
insertions and 3,845 deletions, including 323 public-to-internal visibility
conversions across the App assembly, 55 test files, two retired conformance
tests, and a self-described temporary escape hatch for bridging raw-GL viewport
textures. Even without the regression, that is not separable into the part
worth keeping and the part worth dropping.
Reverting rather than patching because the good work here - the RHI frame
lifecycle wiring and a genuine render-state-cache staleness fix - is small
enough to redo cleanly against a tightened spec, while untangling it from 300+
files of unrelated churn is not.
Post-revert: Release build clean, App suite back to 3,843 passed / 3 skipped,
offline pixel gate passing at 19 differing pixels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 ec414d60
vs this commit): differing fraction 0.318% (1,791/563,200 compared
pixels), above the 0.001 threshold. Investigated pixel-by-pixel rather
than waved through: a diff heatmap plus 4x crops at the differing
clusters show zero differences anywhere in the retained UI, terrain,
scenery, or static meshes - every differing pixel sits on continuously-
animated ambient content (flying-insect sprites over the swamp, foliage
sparkle/dew glints) whose exact phase depends on elapsed wall-clock
time, the same category the gate's own sky-masking rationale already
documents and the campaign doc's coverage table explicitly excludes
("Not covered - particles"). Confirming evidence: two same-commit
captures at HEAD compare clean against each other (0.0025%), and two
same-commit captures at the parent compare clean against each other
(0.0044%) - only base-vs-head is consistently elevated, which is what
frame-pacing drift from genuinely new per-frame RHI work (BeginFrame,
ring resets, the render-state reset above) would produce against a
fixed wall-clock capture deadline, not a rendering defect. Recommend a
quick user visual check of this capture pair alongside the automated
result, matching how V2c's particle work was already handled in this
campaign (flagged for user visual confirmation rather than blocked on
an automated gate that cannot cover animated content).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage.
Co-Authored-By: Codex <noreply@openai.com>
Carry the consumed dark-range child's own retail edge policy into the procedural scrollbar, widening it to the authored post-reflow interval without covering the rendered labels. Restore the bright layer to live attack-charge feedback across the full bar from the left.
Co-Authored-By: Codex <codex@openai.com>
Derive the dark middle range from live post-anchor sibling geometry instead of freezing the inherited 800-pixel prototype during controller binding. Render the user-directed bright desired-power band independently from the absolute bar-left edge to the green thumb center.
Co-Authored-By: Codex <codex@openai.com>
Read the authored meter direction instead of inferring it from the combat element id, so the bright charge texture grows left-to-right from Speed. Keep the user-approved dark-red middle baseline visible independently and record its exact retail Recklessness visibility edge.
Co-Authored-By: Codex <codex@openai.com>
Preserve the authored gray track, trained-Recklessness range, live bright charge meter, and independent desired-power thumb while keeping Speed and Power over gray side regions. Correct retail text justification value 2 to left alignment and retain direct RenderSurface decoding in the texture inspection tool used to verify the assets.
Co-Authored-By: Codex <codex@openai.com>
Resolve authored StringInfo labels from local.dat, port gmCombatUI's runtime option captions and checkbox widgets, and bind horizontal scrollbar media by retail structural roles so the green power jewel is a thumb instead of a tiled track. Persist the three combat options and make Auto Target govern target acquisition.
Co-Authored-By: Codex <codex@openai.com>
Mount authored gmCombatUI, share one press/hold/release request state machine across DAT buttons and keybindings, and recover the exact 1.0s/0.8s power timing from matching retail x86. The same timer fixes jump charge, while ready-stance, response queueing, auto-repeat, layout binding, migration, and conformance coverage keep behavior architectural rather than panel-local.
Co-Authored-By: Codex <codex@openai.com>