From 8e91805206f3537ab5d7130fddb8a4afdf9871b7 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 14 Jun 2026 16:32:27 +0200 Subject: [PATCH] feat(D.2b): Step-0 chrome sprites confirmed + direct-RenderSurface upload path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step-0 prove-out result: retail UI chrome sprites are RenderSurface objects (0x06xxxxxx) that must be decoded DIRECTLY, not via the Surface->SurfaceTexture chain GetOrUpload uses for world materials (which produced 1x1 magenta/garbage). Added TextureCache.GetOrUploadRenderSurface(id, out w, out h) — Portal/HighRes TryGet -> DecodeRenderSurface(palette:null) -> upload, separately cached. This is the path UI chrome + (later) dat fonts use. Confirmed the universal floating-window bevel is an 8-piece border + center fill: center 0x06004CC2 (48x48) edges 0x060074BF/C1 (10x5 horiz) 0x060074C0/C2 (5x10 vert) corners 0x060074C3..C6 (5x5) Recorded in RetailChromeSprites.cs (edge/corner->position mapping is a best guess pending the LayoutDesc 0x21000040 parse; visually confirmed at panel render). The memory-note ids were right; only the decode path was wrong. Temporary prove-out harness (added to GameWindow.OnRender) removed. proveout*.log gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 1 + src/AcDream.App/Rendering/TextureCache.cs | 43 ++++++++++++++++++++ src/AcDream.App/UI/RetailChromeSprites.cs | 48 +++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src/AcDream.App/UI/RetailChromeSprites.cs diff --git a/.gitignore b/.gitignore index ca2f9cf2..215c618b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ references/* /.superpowers/ launch.log launch-*.log +proveout*.log launch.utf8.log n4-verify*.log diff --git a/src/AcDream.App/Rendering/TextureCache.cs b/src/AcDream.App/Rendering/TextureCache.cs index efefbf0b..1fbf0817 100644 --- a/src/AcDream.App/Rendering/TextureCache.cs +++ b/src/AcDream.App/Rendering/TextureCache.cs @@ -31,6 +31,12 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab private readonly Dictionary<(uint surfaceId, uint origTexOverride, ulong paletteHash), uint> _handlesByPalette = new(); private uint _magentaHandle; + // Direct-RenderSurface caches for UI sprites: 0x06xxxxxx RenderSurface ids + // decoded directly (Portal/HighRes → DecodeRenderSurface), bypassing the + // Surface→SurfaceTexture chain that GetOrUpload uses for world materials. + private readonly Dictionary _handlesByRenderSurfaceId = new(); + private readonly Dictionary _rsSizeById = new(); + private readonly Wb.BindlessSupport? _bindless; // Bindless / Texture2DArray parallel caches. Keys mirror the legacy three @@ -103,6 +109,43 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab return h; } + /// + /// Upload a UI sprite by its RenderSurface DataId (0x06xxxxxx), decoded + /// DIRECTLY (Portal/HighRes → DecodeRenderSurface) rather than through the + /// Surface→SurfaceTexture chain that uses + /// for world-geometry materials. This is the correct path for retail UI + /// chrome + font glyph sheets, which reference RenderSurface directly. + /// Palette is null for now (a paletted INDEX16/P8 UI sprite would return + /// Magenta — wire a UI palette when one is actually encountered). Returns a + /// 1x1 magenta handle on miss. + /// + public uint GetOrUploadRenderSurface(uint renderSurfaceId, out int width, out int height) + { + if (_handlesByRenderSurfaceId.TryGetValue(renderSurfaceId, out var existing) + && _rsSizeById.TryGetValue(renderSurfaceId, out var sz)) + { + width = sz.w; height = sz.h; + return existing; + } + + DecodedTexture decoded; + if (_dats.Portal.TryGet(renderSurfaceId, out var rs) + || _dats.HighRes.TryGet(renderSurfaceId, out rs)) + { + decoded = SurfaceDecoder.DecodeRenderSurface(rs, palette: null); + } + else + { + decoded = DecodedTexture.Magenta; + } + + uint h = UploadRgba8(decoded); + _handlesByRenderSurfaceId[renderSurfaceId] = h; + _rsSizeById[renderSurfaceId] = (decoded.Width, decoded.Height); + width = decoded.Width; height = decoded.Height; + return h; + } + /// /// Alpha-channel histogram for one decoded texture. Used to diagnose /// "why are clouds not transparent" — if cloud textures come out with diff --git a/src/AcDream.App/UI/RetailChromeSprites.cs b/src/AcDream.App/UI/RetailChromeSprites.cs new file mode 100644 index 00000000..70a8cb4e --- /dev/null +++ b/src/AcDream.App/UI/RetailChromeSprites.cs @@ -0,0 +1,48 @@ +namespace AcDream.App.UI; + +/// +/// Retail window-chrome RenderSurface DataIds, CONFIRMED via the D.2b Step-0 +/// prove-out (2026-06-14). These are RenderSurface objects (0x06xxxxxx) decoded +/// DIRECTLY (), NOT +/// through the Surface→SurfaceTexture chain. +/// +/// +/// The universal floating-window bevel is an 8-piece border (4 corners +/// 5×5 + 4 edges) drawn around a tiled center fill — it is NOT a single +/// 9-slice texture. Decoded sizes are in the comments (from the prove-out). +/// +/// +/// +/// The edge/corner → position mapping below is a reasonable guess pending the +/// LayoutDesc 0x21000040 parse (sub-project 3) and is confirmed visually in the +/// first vitals-panel render. If a corner's bevel highlight looks wrong, swap +/// the four corner constants; if top/bottom or left/right look inverted, swap +/// those edge pairs. +/// +/// +public static class RetailChromeSprites +{ + /// Tiled interior fill — the shared panel background (48×48). + public const uint CenterFill = 0x06004CC2; + + /// Horizontal top edge (10×5, tiled across the top span). + public const uint TopEdge = 0x060074BF; + /// Horizontal bottom edge (10×5). + public const uint BottomEdge = 0x060074C1; + /// Vertical left edge (5×10). + public const uint LeftEdge = 0x060074C0; + /// Vertical right edge (5×10). + public const uint RightEdge = 0x060074C2; + + /// Top-left corner (5×5). + public const uint CornerTL = 0x060074C3; + /// Top-right corner (5×5). + public const uint CornerTR = 0x060074C4; + /// Bottom-left corner (5×5). + public const uint CornerBL = 0x060074C5; + /// Bottom-right corner (5×5). + public const uint CornerBR = 0x060074C6; + + /// Border thickness in pixels = the corner/edge sprite size (5px). + public const int Border = 5; +}