Skip to main content

UI Modernization (March 25-26, 2026)

Comprehensive UI panel redesign with unified layout system, optimistic updates, and cross-player data leak fixes. Pull Requests: #1088, #1089, #1087
Files Changed: 54 files, ~4,600 additions, ~2,700 deletions
Date: March 25-26, 2026

Overview

This update modernizes the game’s UI panels with a focus on:
  • Visual Consistency: Unified layout constants and theme utilities across all panels
  • Responsiveness: Optimistic UI updates for instant feedback
  • Reliability: Fixed cross-player data leaks and event ordering races
  • Immersion: Heraldic shield combat banners and live 3D equipment preview

Combat Panel Heraldic Shield Redesign

Visual Changes

Replaced vertical combat style list with horizontal heraldic shield banners featuring:
  • SVG Shield Shapes: Custom shield/crest geometry with theme-derived gradients
  • Protruding Icons: Filled geometric icons at top of each shield (accurate = concentric circles, aggressive = double arrows, defensive = shield, controlled = crosshair, rapid = lightning bolt, longrange = arrow, autocast = sparkle)
  • Active State Tinting: Color overlay gradients when style is active
  • Compact Layout: 4 shields in a row with responsive gap sizing

Implementation

Shield SVG Paths (packages/client/src/game/panels/CombatPanel.tsx):
Banner Component:

Optimistic Updates

Combat controls now update instantly before server confirmation:
Impact:
  • Zero perceived latency for combat controls
  • Matches OSRS behavior (instant style switching)
  • Server remains authoritative (can reject invalid changes)

Equipment Panel Paperdoll Portrait

Live 3D Character Preview

Added interactive 3D character preview showing equipped gear in real-time. Features:
  • Live Rendering: Dedicated WebGPU viewport with player’s VRM avatar
  • Equipment Visuals: Dynamically loads and attaches equipped items to VRM skeleton
  • Interactive Controls: Drag to rotate, scroll to zoom
  • Fallback Graphics: Stylized silhouette when avatar unavailable
  • Performance: Shared equipment visual logic between system and portrait

Implementation

Avatar Preview Viewport (packages/client/src/game/character/avatarPreviewViewport.ts):
Equipment Visual Helpers (packages/shared/src/systems/client/EquipmentVisualHelpers.ts): Extracted shared logic from EquipmentVisualSystem for reuse in portrait:
Paperdoll Portrait Component (packages/client/src/game/panels/equipment/EquipmentPaperdollPortrait.tsx):
Paperdoll Grid Layout:
Impact:
  • Immersive equipment preview matching OSRS/RS3 aesthetic
  • Shared equipment visual logic reduces code duplication
  • Reusable viewport factory for other character previews
  • Interactive controls enhance player engagement

Unified Panel Layout Constants

Single Source of Truth

Extracted shared panel dimensions into panelLayout.ts for consistency across all icon-grid panels. Constants (packages/client/src/constants/panelLayout.ts):

Usage Pattern

Before (scattered magic numbers):
After (shared constants):
Panels Updated:
  • InventoryPanel.tsx
  • EquipmentPanel.tsx
  • PrayerPanel.tsx
  • SpellsPanel.tsx
  • SkillsPanel.tsx
  • QuestLog.tsx
Impact:
  • Consistent spacing across all panels
  • Single place to adjust panel dimensions
  • Eliminates scattered magic numbers
  • Mobile and desktop variants clearly defined

CursorTooltip Component

Reusable Tooltip Primitive

Created portal-based mouse-following tooltip with auto-measurement and viewport-edge flipping. Component (packages/client/src/ui/core/tooltip/CursorTooltip.tsx):

Before/After Comparison

Old Pattern (duplicated across 5+ panels):
New Pattern (one line):
Panels Updated:
  • InventoryPanel.tsx - Item tooltips
  • PrayerPanel.tsx - Prayer tooltips
  • SpellsPanel.tsx - Spell tooltips
  • SkillsPanel.tsx - Skill tooltips
  • EquipmentPanel.tsx - Equipment tooltips
Impact:
  • Eliminates ~50 lines of duplicated tooltip code per panel
  • Consistent tooltip behavior across all panels
  • Auto-measurement prevents clipping
  • Viewport-edge flipping for better UX

Tab Persistence System

Problem

Switching tabs would unmount the inactive panel, losing scroll position and component state.

Solution

Render all window tabs simultaneously with display:none/flex toggling instead of unmounting. Implementation (packages/client/src/game/interface/InterfacePanels.tsx):
Impact:
  • Scroll position preserved across tab switches
  • Component state (expanded sections, filters) retained
  • Smoother tab switching experience
  • Matches modern browser tab behavior

Optimistic UI Updates

Combat Controls

Attack Style Changes:
Auto-Retaliate Toggle:

Inventory Actions

Consolidated Rollback System (packages/shared/src/systems/client/ClientNetwork.ts):
Usage (simplified to one line):
Rollback System:
  • Timeout: 5 seconds (if server doesn’t confirm)
  • Pruner: Runs every 1 second to check for stale actions
  • Cleanup: Clears on INVENTORY_UPDATED (server confirmation) or disconnect
  • Shared Tracker: Single PendingActionTracker in ClientNetwork used by all callers
Impact:
  • Instant feedback for eat/drop/bury/firemaking actions
  • Eliminates duplicate tracker instances (two timers, two listeners)
  • Single source of truth for optimistic inventory mutations
  • Reduced ~70 lines of boilerplate across callers
  • Automatic rollback if server doesn’t respond within 5s

Cross-Player Data Leak Fixes

Equipment Panel Leak

Problem: Equipment panel was displaying AI agents’ weapons because equipmentUpdated broadcasts hit all players without filtering. Root Cause (packages/client/src/hooks/usePlayerData.ts):
Fix:
Server Changes (packages/shared/src/systems/client/ClientNetwork.ts):
Impact: Equipment panel now shows only the local player’s gear, eliminates cross-player data leak.

Combat Damage Deduplication

Problem

sendToNearby publishes to 9 region topics (player’s region + 8 adjacent), causing players near region boundaries to receive the same damage packet 2-3 times, resulting in duplicate damage splats.

Solution

Deduplicate using tick-based keys with periodic sweep. Implementation (packages/shared/src/systems/client/ClientNetwork.ts):
Server Changes (packages/server/src/systems/ServerNetwork/event-bridge.ts):
Dedup Strategy:
  • Soft Sweep: Clears entries >500ms old when map exceeds 150 entries
  • Hard Cap: Trims to 100 entries if map exceeds 200 (prevents unbounded growth)
  • Tick-Based Keys: Distinguishes same-damage rapid hits on different ticks
  • Rolling Deploy Fallback: Uses performance.now() / 125 when server tick field is missing
Impact: Eliminates duplicate damage splats near region boundaries, bounded memory usage (max 200 entries).

Attack Style System Cleanup

Removed Dead Code

Removed attack style cooldown infrastructure that was hardcoded to 0ms. Removed:
  • STYLE_CHANGE_COOLDOWN = 0 constant
  • styleChangeTimers Map and timer cleanup logic
  • combatStyleHistory array (write-only, never displayed)
  • lastStyleChange timestamp tracking
  • Dead API methods:
    • canPlayerChangeStyle() - Always returned true
    • getRemainingStyleCooldown() - Always returned 0
    • getPlayerStyleHistory() - Always returned []
Files Changed:
  • packages/shared/src/systems/shared/character/PlayerSystem.ts - Removed cooldown logic (~150 lines)
  • packages/shared/src/systems/shared/infrastructure/SystemLoader.ts - Removed API bindings
  • packages/shared/src/types/entities/player-types.ts - Removed PlayerAttackStyleState fields
Impact:
  • Cleaner codebase with ~200 lines of dead code removed
  • No functional changes (cooldown was already 0ms)
  • Simpler attack style system without unnecessary complexity

Auto-Initialization for Event Ordering Races

Problem

UI events (attack style change, auto-retaliate toggle, equipment updates) can arrive before onPlayerRegister fires, causing “no state for player” errors.

Solution

Added auto-initialization guards that create default state if player exists but hasn’t been registered yet. Attack Style Auto-Init (packages/shared/src/systems/shared/character/PlayerSystem.ts):
Auto-Retaliate Auto-Init:
Equipment Idempotency:
Reconnection Guard:
Impact:
  • Eliminates “no state for player” errors from event ordering races
  • Player choices take precedence over DB-saved values during session
  • Reconnection preserves in-session equipment and combat preferences

Weapon Change Auto-Style Switching

OSRS-Accurate Behavior

Auto-switch attack style when weapon changes and current style is invalid for new weapon. Implementation (packages/shared/src/systems/shared/character/PlayerSystem.ts):
Example: Switching from staff (autocast) to sword → auto-select “accurate” style. Impact: Prevents invalid style errors when weapon changes, OSRS-accurate behavior.

Additional Fixes

Starter Equipment

Change: Fixed STARTER_EQUIPMENT referencing non-existent bronze_swordbronze_shortsword. Files Changed:
  • packages/shared/src/systems/shared/character/InventorySystem.ts
  • packages/shared/src/systems/shared/character/PlayerSystem.ts
  • packages/shared/src/systems/shared/entities/ItemSpawnerSystem.ts
Impact: New players receive correct starter weapon, eliminates item lookup failures.

Fire Model Asset Path

Change: Corrected fire model path from models/firemaking-fire/ to models/misc/firemaking-fire/. Files Changed: packages/shared/src/systems/shared/interaction/ProcessingSystem.ts Impact: Eliminates 404 errors when spawning firemaking fires.

Targeting Mode UI

Changes:
  • Immediate Clear: Targeting state clears immediately after target selection (no server round-trip wait)
  • Hover State: Removed isTargetingActive from slot hover condition to prevent grey flash on all filled slots
  • System Registration: Registered InventoryInteractionSystem on client for targeting support
Impact:
  • Targeting mode feels more responsive
  • No stale highlights after target selection
  • Cleaner visual feedback

Panel Data Synchronization

Problem: WindowRenderer and WindowItem are wrapped in React.memo(), which blocked prop updates when inventory/equipment/stats changed. Solution (packages/client/src/game/interface/InterfaceManager.tsx):
Impact:
  • Inventory panels update in real-time when data changes
  • Lightweight counter (number) breaks memo without forcing panel re-mount
  • renderPanel stays stable (no unnecessary panel recreation)

Event Type Consistency

Change: Replaced raw string event names with EventType enum constants. Implementation (packages/shared/src/systems/shared/entities/Entities.ts):
Impact: Better type safety, prevents typo bugs, improves grep-ability.

Migration Guide

For Developers

Panel Layout Constants:
CursorTooltip Component:
Optimistic Inventory Updates:

For Players

No Breaking Changes: All updates are backward-compatible. Existing characters, inventory, and progress are preserved. New Features:
  • Combat panel now shows horizontal shield banners (more compact)
  • Equipment panel has live 3D character preview (drag to rotate, scroll to zoom)
  • Spells panel added to default layout (check right-column window)
  • Combat controls feel more responsive (instant feedback)
  • Inventory actions (eat, drop, firemaking) update instantly
Visual Changes:
  • Quest log uses themed tiles and badges
  • Panel spacing is more consistent across all panels
  • Tooltips have consistent styling and positioning

Testing

New Test Coverage

Equipment Panel (packages/client/tests/unit/EquipmentPanel.test.tsx):
  • Paperdoll slots render correctly (11 slots)
  • Portrait container exists and shows loading state
  • Equipped items display with icons (no visible item names in slots)
  • Props updates trigger re-render
  • Mobile layout maintains portrait
E2E Tests (packages/client/tests/e2e/panels.spec.ts):
  • Equipment panel renders paperdoll layout on mobile viewport
  • Portrait stays stable during equipment interactions
  • All 11 equipment slots present and functional

Test Commands

Performance Considerations

Tab Persistence Trade-offs

Benefit: Preserves scroll position and component state across tab switches. Cost: All tabs are mounted simultaneously (hidden with display:none). For windows with heavy panels (e.g., 3D equipment portrait), this means the portrait’s WebGPU renderer stays alive even when viewing other tabs. Mitigation: Portrait renderer is lightweight (separate viewport, minimal scene complexity). Future optimization could pause rendering when tab is hidden.

Equipment Portrait WebGPU Context

Resource Usage: Each equipment panel creates its own WebGPU renderer, animation loop, and avatar scene. Considerations:
  • Second WebGPU context alongside main game renderer
  • On lower-end GPUs (especially mobile), this could cause context loss
  • Portrait only renders when equipment panel is visible
Future Optimizations:
  • Share main renderer via render-to-texture
  • Only initialize portrait when panel is actually visible
  • Add cleanup when panel is hidden (not just unmounted)

Optimistic Update Rollback

Memory: PendingActionTracker stores inventory snapshots for up to 5 seconds. Cleanup: Automatic cleanup on server confirmation or disconnect. Bounded: Single shared tracker prevents duplicate instances.

Known Issues

Optimistic Updates Without Rollback

Combat Controls: Optimistic updates for attack style and auto-retaliate don’t have explicit rollback if server rejects the change. Server will send authoritative value, but there could be a brief flash of wrong state. Mitigation: Server rejection is rare (only for invalid weapon/style combinations), and server confirmation arrives within ~100-200ms.

Panel Data Version Pattern

Implementation: Uses useMemo with side effects (mutating panelDataVersionRef.current), which is technically an anti-pattern in React concurrent mode. Risk: React may call memo factories more than once in concurrent mode. Mitigation: Works correctly in current React 19 implementation. Future React upgrades may require refactoring to useRef + useEffect pattern.

Files Changed

PR #1088 (UI Panel Upgrade)

  • 33 files, 4,211 additions, 2,320 deletions
  • Combat panel redesign with heraldic shields
  • Equipment panel paperdoll portrait
  • Unified panel layout constants
  • CursorTooltip component
  • Tab persistence system
  • Quest UI theme modernization

PR #1089 (Equipment Panel Cross-Player Leak)

  • 12 files, 250 additions, 194 deletions
  • Equipment panel playerId filtering
  • Optimistic combat UI updates
  • Attack style cooldown removal
  • Combat damage deduplication
  • Auto-initialization guards
  • Weapon change auto-style switching

PR #1087 (Inventory Firemaking UI)

  • 9 files, 149 additions, 171 deletions
  • Optimistic inventory rollback consolidation
  • Firemaking optimistic removal
  • Fire model asset path fix
  • Targeting mode UI fixes
  • Panel data synchronization fix
Total: 54 files, ~4,600 additions, ~2,700 deletions

References