Skip to content

Add resolution-independent UI scaling and native display/frame pacing #20

Description

@zoeyrose

Important

This preserved product issue is now part of the fresh MIT replacement program. Final implementation owner: atrinik/client. Legacy C/SDL2, packet, global-state, and file-path details below are historical evidence only.

Replacement implementation contract

Preserve the complete UX/display matrix. Establish logical coordinates, DPI/UI scale, fullscreen/windowing, vsync/present mode, frame pacing, power/focus behavior, and accessibility from the outset rather than converting fixed legacy pixels.

New implementation and tests are independent MIT work unless an exact contribution by an approved MIT provenance grantor is admitted through the recorded file-level MIT grant. Preserve every player-facing, accessibility, disclosure, and performance design decision below.

Required verification

  • Test pure state/geometry/material/UI behavior headlessly where possible and run supported Linux/Windows integration paths.
  • Add bounded malformed, stale, lifecycle, resource-loss, and recovery cases appropriate to the owner.
  • Add shared Go/Rust protocol fixtures for authoritative fields; presentation never reconstructs hidden rules.
  • Use released shared renderer/protocol/toolkit contracts and wrapper-managed replacement scenarios.
Preserved product/design specification and historical implementation notes

Why

Atrinik's legacy client can open larger windows, but most of the interface does not become easier to read or operate. The shipped layout is authored in physical pixels around a 1024x768 canvas: client/data/interface.cfg gives the map a fixed 1024x768 surface, client/settings/interface.cfg places HUD/chat/inventory elements at absolute coordinates, and persisted widget overrides store integer x, y, w, and h values. A resize currently changes SDL_SetVideoMode() and moves off-screen widgets back into view; it does not relayout or rescale the interface.

The same assumption exists below the widget layer:

  • fonts are requested at fixed sizes such as Arial 10/11/12 and cached only by family and point size;
  • inventory slots are fixed at 32x32, map projection constants are fixed in pixels, and buttons, borders, panels, cursors, popups, the login screen, and the server browser use fixed-size raster textures and hard-coded offsets;
  • generic popups inherit their dimensions from images such as the 500x300 popup.png, 700x450 book.png, and 450x550 interface.png;
  • the startup screen draws an 800x600 image at (0, 0) and positions the remaining controls with fixed coordinates;
  • the current video settings are a static resolution list, one fullscreen boolean, and 30/60/120/unlimited FPS choices;
  • the main loop uses millisecond SDL_GetTicks()/SDL_Delay() pacing with integer 1000 / fps intervals, while weather/effect motion compensates using the measured number of rendered frames. There is no display-refresh-aware or VSync-owned pacing policy.

The result is especially uncomfortable at 1440p and 4K: increasing resolution mostly exposes more pixels while the interface, text, icons, and world art remain physically tiny. Manual widget resizing cannot solve fixed internal fonts, controls, artwork, popup geometry, or hit targets, and absolute saved coordinates do not survive monitor/DPI/aspect-ratio changes well.

This issue adds a resolution-independent, automatically scaled default interface and modern display/frame-pacing controls after the platform and renderer migrations in atrinik/atrinik#128 and atrinik/client#23. atrinik/client#23 should provide correct raw high-DPI coordinate conversion and renderer transforms; this issue owns the player-visible scale policy, responsive layout, typography, settings, and complete UI conversion. It is also related to the readability request in #28.

Target experience

  • On first run, Auto uses the current display's desktop/native mode, pixel density, and refresh rate. A 1440p or 4K display makes the whole client comfortably larger and sharper instead of merely revealing more map and empty interface space.
  • Windowed, borderless-desktop fullscreen, and exclusive fullscreen are explicit modes. Windowed mode remembers a sensible per-display size; borderless mode follows the desktop mode; exclusive mode exposes only modes actually reported by the selected display, including refresh rate.
  • VSync defaults to enabled/automatic. Frame rate can follow the active display (including 120/144/165/240 Hz), use a selected cap, or be unlimited when VSync is off. Moving the window between displays or changing fullscreen mode refreshes the automatic choice.
  • UI scale and world/scene scale default to Auto. The defaults keep approximately the same readable UI density and map field of view at a given aspect ratio across 1080p, 1440p, and 4K. Wider aspect ratios may reveal more horizontally, but higher pixel density alone does not make everything smaller.
  • Players can override UI scale, text scale, and world scale independently. Fonts are re-rasterized at the effective size; text is never enlarged from a low-resolution cached bitmap.
  • The default HUD is responsive and coherent without manual arrangement. The map fills the available play area; HUD, inventory/player, chat, target, status, minimap, and notifications occupy constrained layout regions and adapt at defined narrow/wide breakpoints.
  • Useful customization remains: chat panel height/width and split, minimap size, text scale, world scale, and selected panel docking/visibility. The default does not expose every internal widget as an unconstrained independently movable pixel rectangle.

Proposed design

1. Establish one display and frame-pacing owner

Build on atrinik/atrinik#128's SDL_Window and atrinik/client#23's renderer abstraction. One client display module should own:

  • selected display, window mode, windowed bounds, drawable/pixel size, desktop and exclusive display modes, refresh rate, and display-scale changes;
  • renderer output size, VSync/present mode, requested frame cap, and effective frame cap;
  • conversion among SDL window/event coordinates, drawable pixels, logical UI coordinates, and world/map coordinates;
  • resize, display-move, DPI/content-scale, minimize/restore, fullscreen, and renderer-recreation notifications.

Replace the static resolution select and fullscreen boolean with live display/mode settings. Do not synthesize a resolution or refresh-rate list. Borderless fullscreen must use the desktop mode without switching it; exclusive fullscreen must preserve the selected display mode and recover safely if it disappears.

Use a high-resolution monotonic clock and deadline-based pacing. VSync should normally own presentation timing; a software limiter should be used only when an explicit lower cap or VSync-off cap requires it, so the client does not double-throttle. Report the selected display, window/drawable sizes, content scale, refresh rate, renderer/backend, VSync state, requested cap, and effective cap in startup diagnostics and the render profiler.

Simulation and effects must use elapsed time rather than rendered-frame counts. Remove effect_frames()/max_frames compensation and audit animation, repeat, tooltip, particle/weather, and input timing so changing from 60 to 240 Hz changes smoothness and latency, not speed or behavior. Static scenes may avoid redundant draws, but continuously animated scenes must be able to present at the selected cadence.

2. Introduce logical UI and world coordinate spaces

Keep four values distinct:

  1. window/event coordinates reported by SDL;
  2. renderer drawable pixels;
  3. density-independent UI units;
  4. map/world units transformed by the camera/world scale.

The automatic UI scale should be derived from drawable size, display content scale, and a documented baseline viewport, then clamped to tested limits. The exact curve should be tuned with screenshots and physical-size testing rather than scattered through widgets. A manual multiplier applies on top of Auto. World scale follows UI scale by default but has its own multiplier so a player may enlarge text/HUD without losing map visibility, or enlarge world graphics independently.

Apply scale in renderer transforms and layout measurement, not by rendering the existing 1024x768 screen to a surface and stretching the final image. Route every pointer coordinate, clip rectangle, drag/resize delta, tooltip anchor, popup hit test, screenshot region, and map pick through the same transforms. Use floating-point layout internally and deterministic rounding at final pixel edges to prevent one-pixel gaps and input/render disagreement.

Map sprites, effects, lightmaps, inventory/object icons, cursors, and UI images must all honor their owning scale. Prefer nearest/integer-aware sampling for pixel art where it preserves the intended look, and appropriate filtered scaling for fractional UI artwork. Keep filtering policy per asset/render class instead of reusing the current global Smooth zoom switch for unrelated content.

3. Replace the absolute default layout with a constrained responsive layout

Add a small purpose-built layout model rather than embedding a general web-style layout engine. The shipped source of truth should describe logical dimensions, anchors/docks, min/max sizes, gaps, ratios, z-order, safe-area insets, and a few aspect-ratio/available-space breakpoints.

Recommended default regions:

  • map/game view fills the viewport or remaining play region;
  • minimap and primary menu/status controls use stable corners/edges;
  • target and compact combat/status information anchor near the game view rather than fixed screen pixels;
  • player doll/inventory use a right-side region on wide layouts and a collapsible/overlaid mode on narrow layouts;
  • chat uses a bottom region with a player-adjustable height and split ratio, with a compact breakpoint rather than overlapping the HUD;
  • modal dialogs are centered within the safe viewport, constrained to a maximum fraction of it, and become scrollable instead of clipping when space is limited.

Layout results should be deterministic from viewport, UI scale, content requirements, and saved user overrides. Save semantic/normalized values such as dock, fraction, logical size, and visibility rather than output pixels. Version the new layout schema and replace the old absolute settings/interface.cfg contract; this repository's greenfield posture does not require preserving a second legacy layout engine. On detecting an old layout, archive or ignore it with a clear message and start from the new responsive default.

Provide a deliberate layout-edit mode only for supported customizations. Normal play should not expose two-pixel resize borders or accidental drag handles on every widget. Keyboard-accessible reset-to-default and per-panel reset actions should remain available.

4. Make controls, typography, and raster UI assets scale-aware

Convert controls from texture-sized geometry to content-sized components with logical padding and minimum hit targets. Render stretchable panels, borders, text inputs, buttons, bars, and dialog frames using nine-slice assets or renderer primitives so their centers can grow without distorting corners. Full-panel artwork should be reserved for genuine illustrations rather than defining control geometry.

Replace widespread fixed font calls with a small semantic type scale (for example caption, body, emphasized body, heading, title, monospace) expressed in logical units. The font cache key must include every rasterization input that affects output, including effective pixel size/scale and style. Reflow and invalidate text/layout caches when scale, display, language/content, or font metrics change.

The settings UI should expose a global text-scale/accessibility override and an optional family choice where the current font inventory supports it. Chat can retain its useful independent text-size override. Per-popup hard-coded point sizes and colors are not a scalable substitute for semantic roles; detailed theme/contrast customization from #28 can remain follow-up work.

Refactor the startup/server browser, login/character screens, settings/keybinding views, NPC interface, books, region map, painting view, tooltips, menus, and notifications onto the same measured controls and dialog layout. It is not sufficient for only in-game movable widgets to scale.

Settings

Recommended player-facing settings:

  • Display: Auto or a reported display
  • Window mode: Windowed / Borderless fullscreen / Exclusive fullscreen
  • Exclusive mode: reported resolution and refresh rate (visible only when applicable)
  • VSync: Auto/On / Off, with adaptive mode only when the selected renderer/backend reports it reliably
  • Frame limit: Follow display / 30 / 60 / 120 / 144 / 165 / 240 / Unlimited (or a validated custom cap)
  • UI scale: Auto plus manual percentage
  • Text scale: percentage relative to UI scale
  • World scale: Auto plus manual percentage
  • Pixel-art filtering: Crisp / Smooth / Auto if testing shows a player choice is useful
  • Default-layout reset and supported panel layout controls

Persist requested policy, not transient detected values. For example, save Follow display, not the current monitor's 144 Hz result. Remember windowed bounds per display and clamp recovered windows to an available work area.

Implementation sequence

  1. Complete Migrate the legacy C client from SDL 1.2 compatibility APIs to SDL3 atrinik#128 and atrinik/client#23, preserving their baseline screenshots and profiler data.
  2. Add display ownership, live mode enumeration, VSync, high-resolution time-based pacing, diagnostics, and the coordinate-space API without changing the visual layout.
  3. Add UI/world scale policy and convert rendering, input, fonts, sprites, cursors, screenshots, and map picking to the shared transforms.
  4. Add the constrained layout model and convert the in-game map/HUD/chat/inventory path as one usable vertical slice.
  5. Convert startup, all popups/dialogs, menus, tooltips, and remaining widgets; replace fixed-size control artwork with measured/nine-slice components.
  6. Remove the absolute-pixel default/persistence path, obsolete scaling helpers, frame-count-dependent motion, and superseded settings.

Acceptance criteria

  • Depends on Migrate the legacy C client from SDL 1.2 compatibility APIs to SDL3 atrinik#128 and atrinik/client#23; no SDL 1.2/window-surface path or parallel software UI renderer is introduced.
  • On 1920x1080, 2560x1440, and 3840x2160 at the same aspect ratio, Auto keeps UI, text, world art, and hit targets at a comparable comfortable apparent scale instead of using pixel density only to expose more content.
  • 16:10, 4:3, ultrawide, and the documented minimum window size produce an intentional breakpoint layout with no overlapping required controls, unreachable dialogs, clipped critical text, or off-screen interaction targets.
  • Fonts are rasterized sharply for the effective output scale and reflow correctly after live UI/text-scale and DPI/display changes.
  • UI art, object icons, map sprites/effects, lighting, cursors, and popup chrome scale through renderer/layout policy; hit tests and map selection agree with rendered positions.
  • Windowed, borderless-desktop, and exclusive fullscreen transitions work repeatedly on Linux and Windows, including moving between displays with different DPI and refresh rates.
  • Auto detects and uses 60/120/144/165/240 Hz display refresh correctly. VSync, explicit caps, unlimited mode, focus loss, and minimize/restore do not busy-spin, double-throttle, or make game/effect timing run faster or slower.
  • The default layout is responsive without manual setup. Players can still adjust chat geometry/split, minimap size, scale, supported docking/visibility, and reset those overrides safely.
  • Startup/server selection, login, character management, settings, keybindings, NPC interfaces, books, region maps, painting, tooltips, menus, and notifications are all usable at every supported scale; this is not limited to the in-game HUD.
  • Persisted display and layout policy survives relaunch and monitor changes without restoring an unavailable mode or an off-screen window.
  • The render profiler exposes effective refresh/cap/VSync, frame/update/present timing, logical and drawable sizes, UI/world scale, and enough renderer data to explain regressions.
  • Linux and portable MinGW builds pass with warnings as errors, and visual/runtime validation is attached for representative low-DPI, high-DPI, high-refresh, fullscreen, ultrawide, and multi-monitor cases.

Validation plan

  • Add deterministic layout/coordinate tests for representative viewport, content-scale, UI-scale, and aspect-ratio matrices. Assert required regions remain within safe bounds, minimum targets are honored, and forward/inverse coordinate transforms agree.
  • Add timing tests with a fake monotonic clock for 60/120/144/165/240 Hz, explicit lower caps, long frames, focus loss, and refresh-rate changes; verify elapsed-time effects are frame-rate independent.
  • Capture before/after full-client screenshots for the existing atrinik/client#23 visual matrix plus 1080p, 1440p, 4K, 16:10, ultrawide, narrow window, and at least two text/UI scale overrides.
  • Exercise every popup and startup flow, long translated/markup-like text, chat selection/input, inventory drag/drop, widget/panel resizing, map targeting, cursor placement, screenshots, and live scale/display changes.
  • Record CPU/GPU frame time, present wait, redraw/upload counts, memory, and input latency on both a map-heavy and UI-heavy scene at 60 Hz and at least one 144 Hz-or-higher display.

Out of scope

  • Replacing the renderer selected in atrinik/client#23 or introducing a second UI framework.
  • Redrawing every game sprite at higher source resolution. The system must scale existing assets correctly and allow density variants later, but an art overhaul should be separately scoped with attribution preserved.
  • Making every widget freely movable/resizable. The goal is a robust responsive default with focused customization, not preserving absolute-pixel desktop composition.
  • Server protocol or gameplay-tick changes. This is client presentation, local input-coordinate, and elapsed-time work.

Semantic-color consumer

The responsive UI, theme, typography, and scale system must host the shared semantic tokens and accessibility behavior defined by #6. It must preserve non-color danger, XP-suitability, rarity, status, warning, and selection cues across supported scales and high-contrast presentation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Fields

    No fields configured for Initiative.

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions