Skip to main content

March 2026 UI & Systems Update

Comprehensive documentation for major UI panel redesign, prayer system fixes, equipment improvements, and combat optimizations merged to main in late March 2026.

Overview

This document covers four major pull requests merged between March 25-26, 2026:
  • PR #1090: Prayer Login Sync Fix
  • PR #1088: Comprehensive UI Panel Upgrade
  • PR #1089: Equipment Panel Cross-Player Leak Fix
  • PR #1087: Inventory UI Fixes for Firemaking and Targeting Mode

Prayer Login Sync Fix (PR #1090)

Problem

Prayer state was not hydrating correctly on login or reconnect. The client bootstrap path was overwriting authoritative cached prayer state with local entity fallback values before real prayer data had finished hydrating, causing the UI to stay stale until the player toggled a prayer again.

Solution

Preserve Authoritative Cache During Hydration:
  • Only use entity prayer fallback when both prayerPoints and maxPrayerPoints are explicitly present and finite
  • Added isFiniteNumber() guard to prevent NaN/Infinity from overwriting cache
  • Rerun initial hydration when local player becomes available via PLAYER_SPAWNED event
Re-emit Prayer State on Join:
  • PrayerSystem now re-emits PRAYER_STATE_SYNC on PLAYER_JOINED event
  • Ensures client receives authoritative prayer snapshot for new session after join
  • Handles both initial login and reconnection scenarios

Implementation

packages/client/src/hooks/usePlayerData.ts:
packages/shared/src/systems/shared/character/PrayerSystem.ts:

Testing

New Tests:
  • packages/client/tests/unit/hooks/usePlayerData.test.ts - Prayer cache preservation, PLAYER_SPAWNED hydration, finite number guards
  • packages/shared/src/systems/shared/character/__tests__/PrayerSystem.sync.test.ts - PLAYER_REGISTERED → PLAYER_JOINED flow
  • packages/client/tests/e2e/prayer-sync.spec.ts - Full login/reload/reconnect flow

Impact

  • Prayer HUD and prayer panel now reflect persisted server state immediately after login or reconnect
  • No more stale prayer UI requiring manual prayer toggle to sync
  • Robust handling of entity data timing races during bootstrap

Comprehensive UI Panel Upgrade (PR #1088)

Overview

Major UI overhaul with 4,211 additions and 2,320 deletions across 33 files. Introduces unified panel layout constants, reusable tooltip component, combat panel redesign with heraldic shields, equipment panel with live 3D paperdoll portrait, and tab persistence improvements.

Unified Panel Layout Constants

New File: packages/client/src/constants/panelLayout.ts Single source of truth for icon-grid panel dimensions used by Inventory, Equipment, Prayer, Spells, and Skills panels. Constants:
Impact:
  • Eliminates scattered magic numbers across 5+ panel components
  • Ensures visual consistency across all icon-grid panels
  • Single place to adjust spacing/sizing for all panels

CursorTooltip Component

New File: packages/client/src/ui/core/tooltip/CursorTooltip.tsx Reusable portal-based mouse-following tooltip that auto-measures and flips orientation when clipping viewport edges. Features:
  • Automatic dimension measurement via useTooltipSize hook
  • Viewport-edge flipping with calculateCursorTooltipPosition
  • Standardized dark gradient theme across game
  • Portal rendering to document.body for z-index safety
API:
Replaced Duplicated Code:
  • InventoryPanel tooltip (manual portal + positioning)
  • EquipmentPanel tooltip (manual portal + positioning)
  • PrayerPanel tooltip (manual portal + positioning)
  • SkillsPanel tooltip (manual portal + positioning)
  • SpellsPanel tooltip (manual portal + positioning)
Impact:
  • Eliminates ~200 lines of duplicated tooltip code
  • Consistent tooltip behavior across all panels
  • Easier to maintain and extend

Combat Panel Redesign

Heraldic Shield Banners: Replaced vertical combat style list with horizontal SVG shield banners featuring:
  • Protruding icons in circular badges above shields
  • Theme-derived gradients (background colors)
  • Style-colored tint overlays when active
  • Filled game-style icons (accurate, aggressive, defensive, controlled, rapid, longrange, autocast)
Visual Structure:
SVG Shield Rendering:
  • Outer shadow edge for depth
  • Base fill with theme gradient
  • Active color tint overlay (when selected)
  • Border stroke (gold inactive, style color active)
  • Top edge highlight for polish
  • Inner bevel for depth
Optimistic State Updates:
Layout Reorganization:
  • Combat style banners moved to top
  • HP + Combat Level below banners
  • Target health (when in combat)
  • Stats row (Attack/Strength/Defense)
  • Auto-retaliate pinned to bottom
Files Changed:
  • packages/client/src/game/panels/CombatPanel.tsx (+1,103/-1,427 lines)

Equipment Panel with Live 3D Paperdoll Portrait

Paperdoll Layout: Equipment slots arranged around a live 3D character preview showing equipped gear in real-time. Grid Layout:
Live Portrait Features:
  • Real-time equipment visualization
  • Drag to rotate character
  • Scroll to zoom (0.4x - 1.15x)
  • Smooth camera interpolation
  • Automatic framing based on avatar bounds
  • Loading/fallback states with stylized silhouette
New Component: EquipmentPaperdollPortrait packages/client/src/game/panels/equipment/EquipmentPaperdollPortrait.tsx (765 lines) Features:
  • Dedicated WebGPU renderer for portrait (separate from main game renderer)
  • Avatar loading from player’s VRM URL
  • Equipment visual attachment using EquipmentVisualHelpers
  • Interactive camera controls (drag rotate, scroll zoom)
  • Three portrait modes: loading, live, fallback
  • Automatic camera framing with zoom compensation
  • Smooth rotation/zoom interpolation (15x lerp factor)
Implementation:
Equipment Visual Helpers Extraction: New File: packages/shared/src/systems/client/EquipmentVisualHelpers.ts Extracted shared equipment attachment logic from EquipmentVisualSystem for reusability: Exported Functions:
  • resolveEquipmentVisualData(options) - Get item metadata
  • resolveEquipmentVisualUrls(options) - Resolve primary/fallback URLs
  • attachEquipmentVisualToVRM(options) - Attach model to VRM bones
  • removeEquipmentVisual(visuals, slot) - Clean up equipment visual
  • extractEquipmentAttachmentData(gltf) - Parse attachment metadata
Types:
  • EquipmentVisualModelData - Item metadata structure
  • EquipmentVisualUrlResolution - URL resolution result
  • EquipmentVisualStore - Visual storage by slot
  • EquipmentAttachmentData - Attachment metadata
Impact:
  • Equipment preview system and main equipment system share same attachment logic
  • Eliminates code duplication
  • Easier to maintain and test
  • Enables future equipment preview features
Slot Redesign:
  • Removed item name labels from slots (icon-only)
  • Quantity badges for stackable items (arrows)
  • Hover tooltips show full item details
  • Empty slot tooltips show slot name
  • Improved visual hierarchy
Footer Buttons:
  • Stats button (opens stats panel)
  • On Death button (shows death mechanics)
Files Changed:
  • packages/client/src/game/panels/EquipmentPanel.tsx (+549/-538 lines)
  • packages/client/src/game/panels/equipment/EquipmentPaperdollPortrait.tsx (new, 765 lines)
  • packages/shared/src/systems/client/EquipmentVisualHelpers.ts (new, 400+ lines)
  • packages/client/src/game/character/avatarPreviewViewport.ts (new, 130 lines)

Tab Persistence

Problem: Switching between tabs (e.g., Prayer ↔ Spells) unmounted inactive tabs, losing scroll position and component state. Solution: Render all 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, selections) maintained
  • Smoother user experience
  • No re-mounting overhead when switching tabs
Trade-off: All tabs are mounted simultaneously, increasing initial render cost but improving UX.

SpellsPanel Added to Default Layout

Schema Migration v17: Added Spells tab alongside Prayer in the default right-column window. packages/client/src/game/interface/DefaultLayoutFactory.ts:
Migration: Existing users get the new tab on next load (schema version bump clears old layouts).

Quest UI Theme Improvements

Themed Components:
  • Quest list items use getInteractiveTileStyle for consistent hover/active states
  • Category groups use getPanelInsetStyle for section headers
  • Detail popup uses getWindowSurfaceStyle for immersive game window feel
  • Compact pill badges for meta info (category, level, status, progress)
Visual Enhancements:
  • State indicator dots with glow effect
  • Category icons (crown, scroll, sun, calendar, star)
  • Progress bars inline with quest items
  • Themed section cards with inset styling
  • Improved mobile layout with touch-friendly targets
Files Changed:
  • packages/client/src/game/components/quest/QuestLog.tsx (+236/-202 lines)
  • packages/client/src/game/panels/QuestsPanel.tsx (+77/-66 lines)

Panel Size Adjustments

Equipment Panel:
  • Min size: 235×290 → 235×340 (taller for portrait)
  • Preferred size: 260×360 → 260×390
  • Max size: 390×550 → 340×520
Tab Chrome:
  • Min height: 34px → 32px (more compact)
  • Padding: reduced for tighter window bars

Equipment Panel Cross-Player Leak Fix (PR #1089)

Problem

Equipment panel was displaying other players’ gear (including AI agents’ weapons) because equipmentUpdated broadcasts hit all players and the UI had no playerId filter.

Solution

Filter Equipment UI Updates by Local Player ID: packages/client/src/hooks/usePlayerData.ts:
Include playerId in Equipment Emissions: packages/shared/src/systems/client/ClientNetwork.ts:

Combat Damage Deduplication

Problem: sendToNearby publishes to 9 region topics, causing players near region boundaries to receive the same combatDamageDealt packet 2-3 times, resulting in duplicate damage splats. Solution: Tick-based deduplication with periodic sweep. packages/shared/src/systems/client/ClientNetwork.ts:
Server-Side Tick Inclusion: packages/server/src/systems/ServerNetwork/event-bridge.ts:
Impact:
  • Eliminates duplicate damage splats near region boundaries
  • Bounded memory usage (max 200 entries)
  • Efficient periodic sweep (only when needed)
  • Handles rolling deploys gracefully (tick fallback)

Optimistic UI Updates for Combat

Attack Style Switching:
  • UI updates instantly on click (no server round-trip delay)
  • Server confirmation overwrites optimistic value with authoritative state
  • Matches OSRS zero-delay feel
Auto-Retaliate Toggle:
  • Same optimistic pattern as attack style
  • Instant visual feedback
  • Server-authoritative confirmation
Implementation Pattern:

Attack Style Cooldown Removal

Removed Dead Code:
  • STYLE_CHANGE_COOLDOWN = 0 (was always zero)
  • styleChangeTimers Map and all timer management
  • combatStyleHistory array (write-only, never displayed)
  • lastStyleChange timestamp tracking
  • canPlayerChangeStyle() method (always returned true)
  • getRemainingStyleCooldown() method (always returned 0)
  • getPlayerStyleHistory() method (always returned [])
Impact:
  • Simplified codebase (~200 lines removed)
  • No functional change (cooldown was already 0ms)
  • Cleaner attack style system

Auto-Style Switching on Weapon Change

OSRS-Accurate Behavior: When weapon changes, validate current style is still available. If not, auto-switch to the first valid style for the new weapon type. Example: Switching from staff (autocast) to sword → auto-select “accurate” packages/shared/src/systems/shared/character/PlayerSystem.ts:

Auto-Initialize Attack Style and Auto-Retaliate

Event Ordering Race Condition: If a player changes attack style or toggles auto-retaliate before onPlayerRegister fires (event ordering race), their in-session choice was being overwritten by the DB-saved value. Solution: Auto-initialize on first use if player exists but onPlayerRegister hasn’t fired yet. packages/shared/src/systems/shared/character/PlayerSystem.ts:
Impact:
  • Player’s in-session choice takes precedence over DB-saved value
  • Updated value persists on next periodic save
  • Handles event ordering races gracefully

Item Rename: bronze_sword → bronze_shortsword

Consistency Update: Renamed starter weapon across all systems for consistency with weapon categorization. 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:
  • Consistent weapon naming across codebase
  • Aligns with weapon type categorization (SHORTSWORD, LONGSWORD, SCIMITAR, 2H_SWORD)

Inventory UI Fixes (PR #1087)

Firemaking Optimistic Inventory Removal

Problem: Logs didn’t disappear from inventory until server confirmed firemaking action (~100-200ms delay). Solution: Optimistic removal using consolidated ClientNetwork API. packages/shared/src/systems/shared/interaction/InventoryInteractionSystem.ts:
Rollback Protection: ClientNetwork.applyOptimisticRemoval() automatically snapshots before mutation and manages rollback internally:
Rollback Mechanism: packages/shared/src/systems/client/ClientNetwork.ts:

Targeting Mode Improvements

Immediate Targeting State Clear: packages/client/src/game/panels/InventoryPanel.tsx:
Impact:
  • No stale highlights on all targets after selection
  • Cleaner UX with instant state reset
  • Prevents visual confusion
Removed Targeting-Dependent Hover State: packages/client/src/game/panels/InventoryPanel.tsx:
Impact:
  • Prevents grey highlight flash on all filled slots when entering targeting mode
  • Simpler hover logic
  • Better visual feedback

Fire Model Asset Path Fix

Corrected Path: packages/shared/src/systems/shared/interaction/ProcessingSystem.ts:

Optimistic Inventory Consolidation

Moved to ClientNetwork: All optimistic inventory logic consolidated from module-level state in InventoryActionDispatcher to ClientNetwork instance methods. Benefits:
  • Eliminates module-level mutable state (trackedWorld, prunerInterval, inventoryTracker)
  • Proper lifecycle management (cleanup on disconnect)
  • Shared tracker for all optimistic actions (eat, drop, bury, firemaking)
  • Single pruner interval instead of multiple
Public API:
Exported Type:
Callers Updated:
  • InventoryActionDispatcher (eat, drop, bury)
  • InventoryInteractionSystem (firemaking)

Combat Rotation Race Condition Fix

Problem: PlayerLocal.update() (hot entity) runs BEFORE TileInterpolator.update() (system), so they fought over base.quaternion every frame. PlayerLocal wrote combat rotation, then TileInterpolator overwrote it with stale state.quaternion, or vice versa. Solution: Route combat rotation through TileInterpolator when it controls the entity. packages/shared/src/entities/player/PlayerLocal.ts:
Impact:
  • Combat rotation flows through TileInterpolator’s state, matching remote players
  • Eliminates race condition between hot entity and system
  • Consistent rotation behavior for local and remote players

API Changes

ClientNetwork Public Methods

New Public Methods:
Exported Types:

Equipment Visual Helpers

New Exports from @hyperscape/shared:
Usage:

PlayerAttackStyleState Type

Removed Fields:

Migration Guide

For Developers

Panel Layout Constants: If you’re creating new icon-grid panels, use the shared constants:
CursorTooltip Component: Replace manual tooltip code with CursorTooltip:
Optimistic Inventory Updates: Use ClientNetwork public API instead of direct cache manipulation:
Attack Style System: Remove references to removed methods:

For Players

Prayer Sync:
  • Prayer state now persists correctly across login/reconnect
  • No manual prayer toggle needed to sync UI
Combat Panel:
  • New shield banner design for attack styles
  • Instant feedback on style changes (no delay)
  • Auto-retaliate toggle responds immediately
Equipment Panel:
  • Live 3D character preview showing equipped gear
  • Drag to rotate, scroll to zoom
  • Click slots to unequip (no modal)
  • Hover for item stats
Spells Panel:
  • Now included in default layout alongside Prayer
  • Access via right-column window tabs
Firemaking:
  • Logs disappear from inventory instantly when lighting fire
  • Smoother visual feedback

Performance Considerations

Tab Persistence Trade-off

Before: Only active tab mounted (minimal memory, re-mount overhead on switch) After: All tabs mounted simultaneously (higher memory, instant switching) Impact:
  • Increased initial render cost (all panels mount at once)
  • Higher memory usage (all panel state in memory)
  • Better UX (instant tab switching, preserved state)
Mitigation: Heavy panels (e.g., EquipmentPaperdollPortrait with WebGPU renderer) should pause expensive operations when tab is hidden.

Equipment Portrait WebGPU Context

Consideration: Each equipment panel creates its own WebGPU renderer, animation loop, and avatar scene. This is a second WebGPU context alongside the main game renderer. Impact:
  • Additional GPU memory usage
  • Potential context loss on lower-end GPUs (especially mobile)
  • Separate render loop overhead
Recommendation: Only initialize portrait when equipment panel is actually visible, dispose when hidden.

Optimistic UI Pattern

Benefits:
  • Instant visual feedback (matches OSRS zero-delay feel)
  • Better perceived performance
  • Reduced user frustration
Risks:
  • UI can show incorrect state if server rejects action
  • Requires rollback mechanism for correctness
  • Increased complexity in state management
Mitigation: All optimistic updates have 5-second rollback timeout and server confirmation overwrites optimistic state.

Testing

New Test Files

Prayer Sync:
  • packages/client/tests/unit/hooks/usePlayerData.test.ts (172 lines)
  • packages/shared/src/systems/shared/character/__tests__/PrayerSystem.sync.test.ts (114 lines)
  • packages/client/tests/e2e/prayer-sync.spec.ts (118 lines)
Equipment Panel:
  • packages/client/tests/unit/EquipmentPanel.test.tsx (204 lines)
  • packages/client/tests/e2e/panels.spec.ts (updated with paperdoll tests)

Test Coverage

Total Tests: 1,569 passing, 85 skipped New Coverage:
  • Prayer cache preservation during hydration
  • PLAYER_SPAWNED re-hydration
  • Finite number guards for prayer points
  • PLAYER_REGISTERED → PLAYER_JOINED sync flow
  • Equipment paperdoll rendering
  • Equipment slot interactions
  • Mobile paperdoll layout
  • Portrait stability during equipment changes

Breaking Changes

None

All changes are backward compatible. Existing layouts will be migrated to schema v17 automatically (clears old layouts to include Spells tab).

Configuration

No New Environment Variables

All changes use existing configuration.

Known Issues

Equipment Portrait Performance

Issue: Equipment portrait creates second WebGPU context, which may cause context loss on lower-end GPUs. Workaround: Portrait only renders when equipment panel is visible. Dispose when panel is closed. Future Fix: Consider sharing main game renderer via render-to-texture or readPixels.

Tab Persistence Memory Usage

Issue: All tabs mounted simultaneously increases memory usage. Workaround: Heavy components should pause expensive operations when tab is hidden. Future Fix: Lazy-mount tabs on first activation, keep alive after first visit.

References

Pull Requests


Credits

Contributors:
  • @SYMBaiEX - UI panel redesign, equipment portrait, quest theme improvements
  • @dreaminglucid - Prayer sync fix, equipment leak fix, inventory optimizations, combat rotation fix
Code Reviews:
  • Multiple rounds of thorough code review ensuring quality and correctness
  • Comprehensive test coverage for all major changes
  • Performance profiling and optimization

Changelog Entry