Character portraits use the same 100-pixel preview slot as vehicle entries.
The surrounding launcher cards, controls and navigation stay as they are.

The preview reads only the character.json manifest and its portrait blob on
a background worker. Both hashes and the catalogue identity are checked.
It reuses the established texture dump decoder for native colour, alpha and
interlacing. Palette portraits use the player's matching imported ROM as data.
No game, guest callback, mod activation, importer or renderer is run to decode.

The worker queue has 32 entries; its immutable image cache has 64. Each image
is at most 256 x 256 RGBA8. SDL textures are made and released on the UI thread,
with 64 cached textures. An eviction never destroys an image referenced by the
current ImGui draw list. Launcher teardown releases textures before the renderer.
Bad or missing preview data cannot prevent the mod card or its controls working.

This layer includes online-performance/ReleaseOverlay.cmake. Use configure.py
here for the combined playtest. The online compatibility IDs are unchanged by
this presentation-only addition. Build DKRCharacterPortraitChecks alongside
the online layer's build.py target set, then run the fixture without a ROM.

The final launcher composition also places the session save/mod summary below
the lobby title and the existing visibility controls below the lobby code.
The header uses two columns when there is room and stacks on smaller windows.
It measures both groups before drawing the player grid. Visibility permissions,
party ownership, save/mod handling and online compatibility IDs are unchanged.

Online Profile places the profile, Privacy and Share a Friend Code cards in
three columns when the main panel is at least 1000 pixels wide. At 660 pixels
it uses two columns; smaller panels stack. Each card keeps at least 320 pixels
in a multi-column row. Row height follows the tallest card so generated codes,
sign-in messages and the readiness section stay clear of one another. Existing
field callbacks, gamepad entry and the right-hand guide keep their behaviour.

Cards in the same Online Profile row now share the measured height of the
tallest card. Outer backgrounds are painted after content measurement; buttons
and fields run once. Save Online Profile has a 12-pixel top gap and fills the
card's content width.

The first-party online_profile module is staged by this existing pipeline.
It adds an optional Discord name/image prompt, background-decoded avatars,
and an owner-only Linux device-session store when Secret Service is unavailable.
Remembered sign-in remains an explicit preference; logout and disabling it
remove the local credential. Windows keeps its existing credential protection.
The matching API bundle adds a private Discord metadata table and endpoint.
Deploy its two SQL migrations and both Workers, keeping current online settings.
Older API deployments still support sign-in; profile import waits for deployment.

Friends use compact cards with up to three columns. Add a Friend is above Search.
Filters, sorting, requests, invites, blocking and nicknames use the existing friend
service. Public lobby players can send or accept a friend request from a player
card. The directory checks the admitted native roster and launcher identity;
display names are never used to decide who receives a request.

Change Profile Picture opens a small dialog. Choose a PNG or JPG up to 8 MB,
preview it, then Save Picture. The background worker crops it to a 128-pixel
square and uploads a fresh PNG without the original metadata. Discord pictures
and initials are also available. Sharing is optional. Transient profile requests
retry five times; validation and sign-in errors return straight away.

Native profile keys are confirmed with a one-use X25519 challenge. The private
key stays in the existing friend service. Each launcher keeps its own friend
identity when multiple computers use the same Discord account. The profile API
uses normal ES modules bundled with pinned esbuild 0.28.2. The existing library,
FTP and account code is retained. No gameplay protocol or rollback ID is changed.
