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>
This commit is contained in:
parent
5b54387b8e
commit
bcc34ee301
22 changed files with 1995 additions and 105 deletions
|
|
@ -107,29 +107,74 @@ namespace AcDream.App.UI;
|
|||
/// </list>
|
||||
/// Register row AP-178 updated to record both dispositions.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// <b>Campaign CH user-gate round 4 (2026-08-10),
|
||||
/// <c>docs/research/2026-08-10-retail-ui-text-style.md</c>:</b> the earlier
|
||||
/// "absent from both dats" finding for the SpewBox's own line template
|
||||
/// (element <c>0x1000004A</c>, base style <c>0x10000377</c> in layout
|
||||
/// <c>0x2100003F</c>) was WRONG — it was missed because the element is a
|
||||
/// ROOT of its layout (a children-only walk skips it) and its font/color
|
||||
/// live in a BaseElement in a DIFFERENT LayoutDesc plus a NAMED state, not
|
||||
/// its own DirectState. Extending the sweep to walk roots, BaseElement/
|
||||
/// BaseLayoutId inheritance, and named-state properties found it, resolving
|
||||
/// three of AP-178's four remaining open sub-claims:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Font:</b> <see cref="RetailFontId"/> is now
|
||||
/// <b>0x40000001</b> (18px bold serif), not the round-3 smallest-font
|
||||
/// heuristic <c>0x40000025</c>. Three independent cross-checks: (1) base
|
||||
/// style <c>0x10000377</c> authors FontDID <c>[0x40000001]</c> directly;
|
||||
/// (2) the line template's authored height is 18px, exactly
|
||||
/// <c>0x40000001</c>'s <c>MaxCharHeight</c>; (3) 4 items × 18px = 72px,
|
||||
/// the authored box height (<see cref="SpewBoxHeight"/>).</item>
|
||||
/// <item><b>Outline:</b> the line template's state <c>0x10000002</c>
|
||||
/// authors property <c>0x21</c> (Outline) = <c>true</c>, outline color
|
||||
/// NOT authored → ctor default black
|
||||
/// (<see cref="UiRenderContext.DefaultOutlineColor"/>). This is the
|
||||
/// "heavy black border" the user's screenshot showed and the earlier
|
||||
/// rounds never reproduced — <see cref="UiText.Outline"/> is now set on
|
||||
/// construction.</item>
|
||||
/// <item><b>Position/extent:</b> confirmed AUTHORED, not merely
|
||||
/// user-matched by luck. <c>0x10000048</c> (the gmSpewBoxUI root) is a
|
||||
/// ROOT element of its own layout, so its parent is the viewport:
|
||||
/// <c>pos(0,0)</c>, edge codes <c>L3/R3</c> ("centered" per
|
||||
/// <c>ElementReader.ToAnchors</c>'s doc comment) and <c>T1</c>
|
||||
/// (top-anchored) resolve to a 450×72 block horizontally centered, flush
|
||||
/// to the viewport top — exactly what <see cref="TopOffset"/>=0 and the
|
||||
/// per-frame centered <c>Left</c> recompute already do. AP-178's position
|
||||
/// sub-claim retires.</item>
|
||||
/// </list>
|
||||
/// Only fill COLOR (see <see cref="SpewBoxColor"/> — the atlas is PFID_A8,
|
||||
/// alpha-only, so it cannot carry baked shading; the user-pinned yellow
|
||||
/// stands pending an exact retail measurement) and the AP-177 line-lifetime
|
||||
/// timeout remain open.
|
||||
/// </remarks>
|
||||
internal sealed class SpewBoxController : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Retail dat Font id this controller resolves for its text
|
||||
/// (Campaign CH user-gate round 3 — see the class remarks). Not
|
||||
/// retail's own measured SpewBox font (unmeasurable — no FontDid
|
||||
/// property was found on the authored element); the smallest DAT font
|
||||
/// confirmed in use by any currently-imported retail LayoutDesc,
|
||||
/// chosen so the rendered text is visibly smaller than the prior debug
|
||||
/// fallback, per the user's report.
|
||||
/// Retail dat Font id this controller resolves for its text. Campaign CH
|
||||
/// user-gate round 4 (2026-08-10): AUTHORED, not a heuristic — base
|
||||
/// style <c>0x10000377</c> (layout <c>0x2100003F</c>), which the
|
||||
/// SpewBox's own line template (<c>0x1000004A</c> in layout
|
||||
/// <c>0x21000011</c>) inherits, authors FontDID <c>[0x40000001]</c>
|
||||
/// directly. <c>0x40000001</c> is the classic AC bold-serif UI face at
|
||||
/// 18px (<c>MaxCharHeight=18</c>, matching the line template's own
|
||||
/// authored 18px height and the 4×18=72px authored box height). See the
|
||||
/// class remarks for the three-way cross-check and
|
||||
/// <c>docs/research/2026-08-10-retail-ui-text-style.md</c> §4.2.
|
||||
/// </summary>
|
||||
internal const uint RetailFontId = 0x40000025u;
|
||||
internal const uint RetailFontId = 0x40000001u;
|
||||
|
||||
/// <summary>
|
||||
/// Register row AP-178 (screen position): retail's authored ABSOLUTE
|
||||
/// screen position is still unknown — the LayoutDesc dump (see class
|
||||
/// remarks) recovered the element's position as <c>(0,0)</c> relative
|
||||
/// to a PARENT this sweep could not identify. Campaign CH user-gate
|
||||
/// round 3 (2026-08-10): the user reported the box was not flush to
|
||||
/// the very top of the screen; mounted at <c>0</c> now, per explicit
|
||||
/// user direction — still acdream's own placement choice pending the
|
||||
/// true retail parent/offset, but now matching the user's live report
|
||||
/// instead of an arbitrary 60px placeholder. (The SIBLING row AP-177 —
|
||||
/// Register row AP-178 (screen position): CONFIRMED authored, not a
|
||||
/// placeholder that happens to match. Campaign CH user-gate round 4
|
||||
/// (2026-08-10): the SpewBox root (<c>0x10000048</c>) is a ROOT element
|
||||
/// of its own layout (<c>0x21000011</c>), so its parent is the
|
||||
/// viewport — <c>pos(0,0)</c> plus edge codes <c>L3/R3</c> (centered)
|
||||
/// and <c>T1</c> (top-anchored) resolve to exactly this: a fixed-width
|
||||
/// block horizontally centered and flush to the viewport top. The
|
||||
/// round-3 "user-directed approximation pending the true retail
|
||||
/// parent/offset" framing is retired — the parent (the viewport root)
|
||||
/// and the offset (0) are now both resolved. (The SIBLING row AP-177 —
|
||||
/// the invented line-lifetime timeout — lives in
|
||||
/// <see cref="SpewBoxState.DefaultLifetime"/>'s own doc comment, not
|
||||
/// here; this controller does not own that concern.)
|
||||
|
|
@ -173,13 +218,22 @@ internal sealed class SpewBoxController : IDisposable
|
|||
/// (<c>colorBrightRed</c>) is still explicitly NOT this — retail's own
|
||||
/// <c>BuildChatColorLookupTable</c> writes to <c>ChatInterface::m_chatLog</c>,
|
||||
/// a completely different element tree the SpewBox never touches
|
||||
/// (research doc §3.2.3); the LayoutDesc dump (see class remarks) also
|
||||
/// never surfaced a colour property for this element. The exact retail
|
||||
/// value simply happens to coincide with the Tell colour, per the user's
|
||||
/// live observation. POSITION and FONT were re-addressed at Campaign CH
|
||||
/// user-gate round 3 (2026-08-10) — see the class remarks and the
|
||||
/// <see cref="TopOffset"/>/<see cref="RetailFontId"/> comments; both
|
||||
/// remain acdream-directed approximations, not resolved retail values.
|
||||
/// (research doc §3.2.3). POSITION and FONT were resolved as AUTHORED at
|
||||
/// Campaign CH user-gate round 4 (2026-08-10) — see the class remarks and
|
||||
/// the <see cref="TopOffset"/>/<see cref="RetailFontId"/> comments. The
|
||||
/// AUTHORED value for THIS element's state <c>0x10000002</c> is actually
|
||||
/// pure red <c>ARGB(255,255,0,0)</c> — but the user's live retail
|
||||
/// screenshot shows gold/amber, not red, so the user's own eye remains
|
||||
/// the axiom here (per <c>feedback_retail_oracle_no_whack_a_mole</c>).
|
||||
/// Round 4 checked whether font <c>0x40000001</c>'s FILL-plane atlas
|
||||
/// could explain the gold as baked shading over the pinned yellow: it
|
||||
/// cannot — every dat font atlas (this one included) is <c>PFID_A8</c>,
|
||||
/// alpha-only coverage with no per-pixel colour channel, so there is no
|
||||
/// baked tint to compose with. The gold reading is therefore the OUTLINE
|
||||
/// itself (a black border around a bright yellow glyph reads warmer/
|
||||
/// richer than the same colour drawn flat) rather than atlas shading.
|
||||
/// The user-pinned yellow stands; only an exact cdb capture of live
|
||||
/// retail's <c>m_curFontColor</c> would resolve the remaining gap.
|
||||
/// </summary>
|
||||
private static readonly Vector4 SpewBoxColor = new(1f, 1f, 0.247f, 1f);
|
||||
|
||||
|
|
@ -245,6 +299,12 @@ internal sealed class SpewBoxController : IDisposable
|
|||
ClickThrough = true,
|
||||
ZOrder = int.MaxValue,
|
||||
DefaultColor = SpewBoxColor,
|
||||
// Campaign CH user-gate round 4: the line template's authored state
|
||||
// 0x10000002 sets property 0x21 (Outline) = true with no authored
|
||||
// outline colour, i.e. the ctor black default
|
||||
// (UiRenderContext.DefaultOutlineColor) — the heavy black border in
|
||||
// the user's screenshot. See the class remarks.
|
||||
Outline = true,
|
||||
Visible = false,
|
||||
};
|
||||
_text.LinesProvider = () => _lines;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue