Combat System
Hyperscape implements a tick-based combat system inspired by Old School RuneScape. Combat operates on discrete 600ms ticks, with authentic damage formulas, accuracy rolls, and attack styles.Combat code lives in
packages/shared/src/systems/shared/combat/ and uses constants from packages/shared/src/constants/CombatConstants.ts.Core Constants
FromCombatConstants.ts:
Mob Combat
Mobs can use any of the three attack types: melee, ranged, or magic. This is configured in the NPC manifest and fully integrated with the combat system.Mob Attack Types
Configure mob attack types via NPC manifest JSON:attackType:"melee"(default),"ranged", or"magic"spellId: Required for magic mobs (e.g.,"wind_strike","fire_bolt")arrowId: Required for ranged mobs (e.g.,"bronze_arrow","iron_arrow")heldWeaponModel: Optional visual weapon GLB (bow, staff, etc.)combatRange: Attack range in tiles (1 for melee, 7-10 for ranged/magic)attackSpeedTicks: Ticks between attacks (4-5 typical)
Mob Combat Mechanics
Mobs use the same combat handlers as players but with simplified resource management: Mob Magic Attacks:- Use mob’s
magicstat for damage calculation - No rune consumption (infinite resources)
- Emit spell projectiles with correct visual effects (element-based colors)
- Play
SPELL_CASTanimation - Spell launch delay: 600ms (allows cast animation wind-up)
- Hit delay formula:
1 + floor((1 + distance) / 3)ticks
- Use mob’s
rangedstat for damage calculation - No arrow consumption (infinite resources)
- Emit arrow projectiles with correct visuals (metal-tipped arrows)
- Play
RANGEanimation - Arrow launch delay: 400ms (allows draw animation wind-up)
- Hit delay formula:
1 + floor((3 + distance) / 6)ticks
- Use mob’s
attackandstrengthstats - Standard melee range (1 tile) or custom
combatRange - Play
COMBATorSWORD_SWINGanimation - Immediate hit (0 tick delay)
Held Weapon Visuals
Mobs can display held weapons (bows, staves) using the same attachment system as player equipment:- Static
_weaponCacheshares loaded GLB scenes across mob instances _pendingLoadsdeduplicates concurrent fetches for the same URLclone(true)creates per-mob instances with shared geometry/materials- Cache cleared on world teardown via
MobNPCSpawnerSystem.destroy() - Prevents duplicate network requests and GPU memory waste
- Proper cleanup: weapons removed from parent on mob destroy (geometry shared, not disposed)
vrmBoneName: Target bone (default:"rightHand")version: Metadata format version (1 or 2)relativeMatrix: Pre-baked 4×4 transform matrix (V2 format)
- V1: Direct attachment to bone (simple position/rotation)
- V2: Pre-baked matrix with
EquipmentWrappergroup (advanced positioning)
Mob Damage Calculation
Mobs use simplified damage formulas without equipment bonuses:- Mobs have zero equipment bonuses (no attack/strength/defense from gear)
- Mobs have infinite resources (no rune/arrow consumption)
- Mobs use stats from NPC manifest (not dynamic equipment)
- Players can still use prayer bonuses to defend against mob attacks
- Damage formulas are otherwise identical to player formulas (OSRS-accurate)
Projectile Emission
Both magic and ranged attacks emit projectile events for client-side visuals:delayMs: Time after attack start before projectile appears (animation wind-up)travelDurationMs: How long the projectile flies (derived from hit delay formula)- Visual arrival coincides with server-side damage splat
- Spell projectiles: 600ms delay (allows staff raise and cast gesture)
- Arrow projectiles: 400ms delay (allows bow draw animation)
Mob Retaliation
When a player attacks a ranged or magic mob, the mob retaliates with the correct attack type:Attack Handler Routing
The combat system routes mob attacks through specialized handlers:-
Initial attack (first hit when entering combat):
MobEntity.performAttackAction()emitsCOMBAT_MOB_NPC_ATTACKevent- Event carries
attackType,spellId,arrowIdfrom mob config CombatSystem.handleMobAttack()routes to appropriate handler- Handler validates, calculates damage, emits projectile
- Calls
enterCombat()withweaponTypeto store attack type
-
Auto-attack ticks (subsequent attacks while in combat):
CombatTickProcessor.processAutoAttackOnTick()readscombatState.weaponType- Routes through
CombatSystem.handleAttack()to appropriate handler - Handler resolves
spellId/arrowIdfrom NPC data viagetNPCById() - No event data needed—attack type persisted in combat state
MagicAttackHandler, RangedAttackHandler, MeleeAttackHandler), ensuring consistent behavior.
Shared Attack Preparation
TheprepareMobAttack() utility in AttackContext.ts handles common validation for mob projectile attacks:
- Entity Resolution: Resolve attacker and target entities (or use
preResolvedto avoid double lookup) - Alive Check: Verify both entities are alive
- NPC Data Lookup: Get mob configuration from
getNPCById(mobData.type) - Range Validation: Check distance using
checkProjectileRange()with mob’scombatRange - Position Validation: Get entity positions via
getEntityPosition() - Cooldown Check: Verify attack is not on cooldown
- Cooldown Claim: Set
nextAttackTicksto prevent rapid-fire attacks - Face Target: Rotate mob to face target via
rotationManager - Play Animation: Trigger attack animation via
animationManager
- Returns
MobAttackContextwith all validated state if all checks pass - Returns
nullif any check fails (attack aborted, no error thrown)
preResolved parameter allows handlers to pass already-resolved mob entity and NPC data, avoiding redundant entityResolver.resolve() and getNPCById() calls when the handler needs this data for spell/arrow validation before calling prepareMobAttack().
Session Interruption
Combat Closes Bank/Store/Dialogue
When a player is attacked, all interaction sessions are automatically closed (OSRS-accurate behavior):user_action— Player explicitly closed UIdistance— Player moved too far from targetdisconnect— Player disconnectednew_session— Replaced by new sessiontarget_gone— Target entity no longer existscombat— Player was attacked (OSRS-style)
OSRS-Accurate: Even a splash attack (0 damage) closes the bank/store/dialogue. Being in combat matters, not just taking damage.
Food Consumption & Combat
Eat Delay Mechanics
Food consumption integrates with the combat system using OSRS-accurate timing:- 3-tick delay between eating (1.8 seconds)
- Food consumed even at full health
- Attack delay only added if already on cooldown
- If weapon is ready to attack, eating does NOT add delay
Attack Delay Integration
When eating during combat, the system checks if the player is on attack cooldown:- Player attacks with longsword (4-tick weapon)
- Attack lands at tick 100, next attack at tick 104
- Player eats at tick 102 (while on cooldown)
- Eat delay adds 3 ticks: next attack now at tick 107
- If player eats at tick 104+ (weapon ready), no delay added
Healing Formula
Healing is capped and validated server-side:Combat Styles
Combat styles determine which skill gains XP and provide stat bonuses.Melee Styles
Ranged Styles
Magic Styles
OSRS-Accurate: Staves and wands have both melee and magic combat styles. When used without a spell selected, they function as crush weapons and grant melee XP.
Staff/Wand Magic Styles (with spell selected):
Damage Calculation
Damage uses the authentic OSRS formula from the wiki.Per-Style Combat Bonuses
The armor system implements OSRS-accurate per-style attack and defense bonuses: Melee Attack Styles:- Stab: Daggers, spears (piercing attacks)
- Slash: Swords, scimitars, axes (slicing attacks)
- Crush: Maces, unarmed (blunt attacks)
Armor Defense Bonuses:
Each armor piece provides separate defense values for each attack style:
- Melee Armor: High stab/slash/crush/ranged defense, negative magic bonuses
- Ranged Armor: Positive ranged/magic defense, lower melee defense
- Magic Armor: Positive magic attack/defense, minimal physical defense
The combat system automatically selects the appropriate attack/defense bonus based on weapon type. For example, a sword attack uses
attackSlash vs. the defender’s defenseSlash.Maximum Hit Formula
Prayer bonuses are applied before the effective level calculation, matching OSRS mechanics.
Accuracy Formula (Per-Style)
Prayer bonuses apply to both attacker and defender, affecting accuracy and defense rolls. Per-style bonuses fall back to generic bonuses for backward compatibility.
Damage Roll
Ranged Combat
Ranged combat uses bows and arrows with OSRS-accurate mechanics.Requirements
- Bow equipped in weapon slot
- Arrows equipped in ammo slot
- Sufficient Ranged level for bow
Available Bows (F2P)
Available Arrows (F2P)
Ranged Damage Formula
Projectile System
Ranged attacks create projectiles with OSRS-accurate hit delays:- 3D arrow meshes with metal-colored tips
- Arc trajectory (straight line, no gravity arc in F2P)
- Rotates to face travel direction
- Delayed hit based on distance
Ammunition Consumption
Arrows are consumed on every shot:Ranged XP
Magic Combat
Magic combat allows spellcasting with or without a staff.Requirements
- Magic level sufficient for the spell
- Runes in inventory (or infinite from elemental staff)
- Spell selected for autocast (optional)
OSRS-Accurate: You can cast spells without a staff. The staff provides magic attack bonus and infinite runes for its element.
Available Spells (F2P)
Strike Tier (Levels 1-13):
Bolt Tier (Levels 17-35):
Elemental Staves
Staves provide infinite runes for their element:Magic Damage Formula
Key Difference: Magic defense for players uses 70% Magic level + 30% Defense level. NPCs only use Magic level.
Rune Consumption
Runes are consumed on each cast:Autocast
Players can select a spell for autocast:- Selected spell automatically casts when attacking
- Spell selection persists across sessions (saved in database)
- Cleared when weapon is changed
- Shown in Spells panel with checkmark
Spell Projectiles
Magic spells render as multi-layer billboard meshes with WebGPU-compatible DataTextures:- Outer Glow (Layer 1): Soft, semi-transparent, 2.5× size
- Core Orb (Layer 2): Bright center, sharp glow
- Orbiting Sparks (Layer 3, bolt-tier only): 2 tiny particles that orbit the core
material.color tinting) for reliable rendering in WebGPU:
Magic XP
Attack Type Detection
The combat system automatically detects attack type from equipped weapon or selected spell:Spell Priority: If a spell is selected for autocast, the attack type is MAGIC regardless of equipped weapon. This allows staffless casting (OSRS-accurate).
Attack Range System
Melee Range
Combat Pathfinding
Combat movement uses multi-destination BFS with line-of-sight validation for OSRS-accurate pathfinding:- BFS as primary pathfinder: Player movement uses BFS (“smartpathing”) instead of naive diagonal-first
- Multi-destination search: Generates all valid attack tiles and finds shortest path to any of them
- Line of sight: Ranged/magic attacks verify LoS via Bresenham line trace against
BLOCKS_RANGEDcollision flags - Optimal pathing: Naturally selects the closest reachable attack position
- Obstacle handling: Automatically routes around walls and blocked tiles
- Eliminates visible diagonal zigzag when walking to attack targets
- Prevents attacking through walls when in Chebyshev range
- Finds optimal path to closest reachable attack position
- Matches OSRS smartpathing behavior for players
Combat Follow System
OSRS-Accurate: Players continuously follow their combat target while in range, not just when out of range. This prevents the stutter pattern where players stand still until the target moves away.
- Smooth pursuit of moving targets
- Zero-delay response when target leaves range
- Pre-computed pathfinding for responsive gameplay
- Matches OSRS behavior where players “stick” to their target
lastCombatTargetTile map is automatically cleaned up when:
- Combat ends naturally (timeout)
- Player disengages from combat
- Combat is forcibly stopped
Combat Rotation System
Players automatically face their combat target during ranged and magic attacks:- Combat target dies
- Player starts moving (movement takes priority)
- Combat ends or is disengaged
Ranged Combat
Ranged attacks use Chebyshev distance and require:- A ranged weapon (bow)
- Ammunition (arrows)
- Shortbow, Oak Shortbow, Willow Shortbow, Maple Shortbow
- Models are properly rigged to the player’s hand bones
- Bows are visible during idle, walking, and combat animations
Attack Speed & Cooldowns
Attacks occur on tick boundaries with weapon-specific speeds.Weapon Speed Examples
Aggro System
NPCs have configurable aggression behaviors.Aggro Types
Aggro Constants
Aggro Logic
Death Mechanics
Player Death
When a player dies:- Headstone spawns at death location
- Items drop to headstone (kept for 15 minutes)
- Player respawns at starter town
- 3 most valuable items are kept (Protect Item prayer adds 1)
Duel Death Handling
Duel deaths use individual try/catch blocks to prevent one player’s failure from affecting the other:restorePlayerHealth calls were previously in a single try/catch. If the winner’s restore triggered an exception in any PLAYER_RESPAWNED handler, the loser’s restore was skipped—leaving them with:
- Frozen physics (
isDying=true) - No
playerRespawned/playerSetDeadpackets sent - Client unable to act on
playerTeleport(which still arrived) - Player stuck in arena with death animation
Mob Death
When a mob dies:- Loot drops based on drop table
- XP granted to all attackers
- Respawn timer starts (based on mob type)
- Entity destroyed after death animation
XP Distribution
XP is granted based on damage dealt and combat style.Melee XP
Ranged XP
Magic XP
Magic grants XP even on a splash (0 damage). You still get the base spell XP, just no damage bonus.
PvP XP Calculation
In player-versus-player combat (duels), XP is granted based on the actual weapon type used, not the player’s selected attack style:Fixed in PR #875: Previously, PvP kills always used the player’s melee attack style, causing ranged attacks to incorrectly grant Strength XP. Now the system inspects the actual weapon type, matching the logic used for mob kills.
Food & Combat Interaction
Eating During Combat
When a player eats food while in combat, OSRS-accurate timing rules apply:Eat Delay Mechanics
Players cannot eat again until the eat delay expires:OSRS-Accurate: Food is consumed even at full health. The eat delay and attack delay apply regardless of current HP.
Attack Delay API
TheCombatSystem provides methods for eat delay integration:
Combat Events
The combat system emits events for UI and logging:Prayer Bonuses in Combat
Active prayers provide multipliers to combat stats. See Prayer System for complete details. Example Prayer Effects:- Clarity of Thought (Level 7): +5% Attack (1.05× multiplier)
- Burst of Strength (Level 4): +5% Strength (1.05× multiplier)
- Thick Skin (Level 1): +5% Defense (1.05× multiplier)
Combat Visual Synchronization
Mob Combat Rotation
Mobs properly clear their combat rotation flag when movement starts to prevent stuck facing:Damage Splat Positioning
Damage splats now use entity visual position instead of server position for accurate placement:Movement Packet Optimization
The server sendsemote on tileMovementEnd instead of redundant entityModified (PR #884):
cancelMovement now sends tileMovementEnd so client TileInterpolator properly stops interpolating:
entityModified sent on cancel, client kept interpolating old path
After Fix: tileMovementEnd sent, client properly stops interpolation
Duel Arena Combat
AI Combat Timing
TheDuelCombatAI system manages automated agent duels with simplified attack logic (commit 51453da):
- Removed redundant attack-speed tracking: The combat system’s auto-attack loop already drives attack cadence
- Simplified AI logic: AI only re-engages when combat drops or target changes
- Fixed 2H sword attacks: Previously, manual attack-speed tracking competed with the combat system’s auto-attack loop, causing attacks to be silently dropped (especially for slow weapons like 2H swords)
- Added TWO_HAND_SWORD default style: Missing default attack style for two-handed swords added to
WeaponStyleConfig.ts
- Combat system’s
processAutoAttackOnTickhandles all attack timing once combat is established - Calling
executeAttackon every cooldown cycle creates a redundant second driver - Both drivers compete for the same cooldown slot, silently dropping attacks
- AI now only calls
executeAttackwhen combat needs to be (re-)established
WeaponStyleConfig.ts:
Teleport Suppression
Duel arena teleports can be suppressed to prevent visual effects during fight cleanup:- Fight-start HP sync:
restoreHealth()withquietparam skipsPLAYER_RESPAWNED/PLAYER_SET_DEADevents - Proximity corrections: Teleports to fix position without visual disruption
- Cleanup teleports: Return players to lobby without teleport animation
Cycle Cleanup Chaining
The duel scheduler chains cleanup → delay → new cycle via.finally() to prevent stale avatars:
- Cleanup always teleports both agents (even if errors occur)
- Delay ensures clean state before next cycle
- Prevents stale avatars from previous fights
Countdown Overlay
The countdown overlay stays mounted 2.5s into FIGHTING phase with fade-out animation:Arena Visuals
Fence Design:- Replaced solid walls with fence posts + rails for better visibility
- Allows spectators to see into the arena
- Maintains collision boundaries
- Procedural sandstone tile pattern for OSRS medieval aesthetic
- Each arena gets unique randomized texture with grout lines, color variation, and speckle noise
- Canvas-generated at runtime (no texture files needed)
- Lit torches with fire particles at all 4 corners of each arena
- PointLights with flicker animation
- “torch” glow preset (6 riseSpread particles per torch, tight 0.08 spread)
Health Bar Synchronization
Health bars are synchronized inline inhandleEntityDamaged before every broadcast:
- Health bars always reflect current HP
- No race conditions between damage and HP updates
- Spectators see accurate health in real-time
AI Agent Trash Talk System
TheDuelCombatAI system includes an integrated trash talk feature that allows AI agents to taunt opponents during combat:
Trash Talk Triggers
Health Threshold Taunts: Triggered when HP crosses specific milestones (75%, 50%, 25%, 10%):LLM-Generated Taunts
When an Eliza runtime is available, trash talk uses the agent’s character personality:Scripted Fallback Taunts
When no LLM runtime is available or LLM calls fail, the system uses pre-written taunt pools:Fire-and-Forget Architecture
All trash talk calls are background/non-blocking to prevent combat tick delays:- Never blocks combat tick loop
- 8-second cooldown prevents spam
- In-flight flag prevents overlapping LLM calls
- Failures are silent (trash talk is optional flavor)
Combat Role System
The duel system supports three combat roles with automatic gear provisioning and weighted random selection (added in PR #933, Feb 2026): Combat Roles:- Melee (50% weight): Bronze weapons (longsword, scimitar, 2h sword)
- Ranged (25% weight): Shortbow + bronze arrows (500 qty), uses “rapid” attack style
- Mage (25% weight): Staff of air + wind strike autocast + runes (500 mind, 500 air)
- Melee: Uses existing phase-based style switching (aggressive/controlled/defensive)
- Ranged: Forces “rapid” style for faster attack speed (-1 tick), skips melee style switching
- Mage: Skips style switching entirely (magic auto-casts via selectedSpell)
- Pre-duel: Role selected → gear equipped → food filled → health restored
- During duel: Combat AI adapts behavior based on assigned role
- Post-duel: Gear removed → runes removed → food removed → health restored → teleport back
equipMeleeWeapon(): Random bronze weapon from pool (longsword, scimitar, 2h sword)equipRangedGear(): Shortbow + bronze arrows (500 qty)equipMageGear(): Staff of air + wind strike autocast + runes (500 mind, 500 air)cleanupAgentCombatSetup(): Unequips all combat gear, clears autocast, removes leftover runes
swords/ directory are eligible for duel arenas:
Weapon Type Propagation (PR #934, commit 029456255, Feb 25, 2026)
The combat system now propagates weapon type throughDuelOrchestrator into startCombat so correct attack speeds are used:
- Problem: Entity data flags (
inCombat,combatTarget) can be stale when CombatSystem’s internal state has timed out. Agents would stand idle instead of attacking. - Fix: DuelCombatAI now periodically re-engages every 5 ticks (~3s) as a keep-alive, even when entity flags show combat is active
- Impact: 2H sword attacks now fire reliably, agents no longer idle during combat
- Problem: Adding runes before inventory loaded from DB caused silent rune loss.
getOrCreateInventoryreturned a disposable placeholder (not stored in Map). - Fix: Wait up to 2 seconds for
inventorySystem.isInventoryReady(playerId)before adding runes - Impact: Mage agents now reliably receive runes, magic attacks work consistently
- Problem: Ranged/magic attacks didn’t refresh combat timeout, causing combat to expire after 16 ticks even during active fighting
- Fix: Both
CombatSystemandCombatTickProcessornow refreshcombatEndTickafter ranged/magic attacks - Impact: Combat stays active during ranged/magic fights, no premature timeout
- Problem: Streaming duel agents couldn’t fight in safe zones due to PvP zone checks
- Fix: Bypass PvP zone checks when
entity.data.inStreamingDuel === true - Impact: Streaming duels work in any zone, not just wilderness
- Problem: Hostile mobs would aggro and chase players in safe zones
- Fix:
AggroSystemnow checksZoneDetectionSystem.isSafeZone()before aggroing or chasing - Impact: Safe zones are truly safe, mobs won’t attack players there
Combat State Starvation Guard (PR #934)
The system now guards against state starvation from repeatedstartCombat resets on slow weapons:
createAttackerStatereplaces the state Map entry which resetsnextAttackTick- For slow weapons (2H swords, attackSpeed 7), repeated re-engagement keeps pushing
nextAttackTickforward - Auto-attack loop never reaches
nextAttackTick(starvation pattern) - Guard prevents replacing valid combat state, allowing auto-attacks to fire
Critical Bug Fixes
Combat State Key Mismatch (Fixed in PR #933):- Issue: CombatStateService syncs abbreviated keys (
data.c/data.ct) butgetGameState()only read full keys (data.inCombat/data.combatTarget) - Impact: DuelCombatAI always saw
inCombat=falseand floodedexecuteAttackevery tick instead of letting auto-attacks drive combat - Fix: EmbeddedHyperscapeService now reads both abbreviated and full keys:
- File:
packages/server/src/eliza/EmbeddedHyperscapeService.ts - Commit: 82ff784 (Feb 25, 2026)
- Issue: Cooldown was checked early but claimed after async
consumeRunesForSpellcall. With the combat state bug flooding attacks, two concurrent invocations could both pass the cooldown check before either claimed it - Impact: Duplicate magic projectiles, double rune consumption
- Fix: Moved cooldown claim and
enterCombatbefore async rune consumption to close the race window - File:
packages/shared/src/systems/shared/combat/handlers/MagicAttackHandler.ts - Commit: 82ff784 (Feb 25, 2026)
- Issue: Players/agents were sinking ~0.4m into duel arena floors because flat zones were removed from the terrain system, causing
getHeightAt()to return raw procedural terrain height instead of floor-level height - Impact: Players appeared to sink through arena floors, grass grew through floor surfaces
- Fix: DuelArenaVisualsSystem now registers flat zones programmatically for all 8 floor areas (6 arenas + lobby + hospital) so terrain height queries return correct floor-level values
- File:
packages/shared/src/systems/client/DuelArenaVisualsSystem.ts - Commit: 7a60135 (Feb 25, 2026)
- Issue: Click targets were going underground in duel arenas due to building footprint validation rejecting arena floor raycast hits
- Impact: Players couldn’t click to move within duel arenas
- Fix: RaycastService now skips building footprint validation for arena-floor raycast hits
- File:
packages/shared/src/systems/client/interaction/services/RaycastService.ts - Commit: 2435423 (Feb 24, 2026)
- Issue: Minimap showed duel arenas as black holes because arena/lobby/hospital floor meshes had layer 0 disabled
- Impact: Minimap was unusable in duel arena area
- Fix: Enabled layer 0 on arena/lobby/hospital floor meshes so minimap camera can render them
- File:
packages/shared/src/systems/client/DuelArenaVisualsSystem.ts - Commit: 2435423 (Feb 24, 2026)
- Issue: 96 dead wall sconce meshes across 6 arenas with no lights attached
- Impact: Unnecessary geometry in scene, no visual benefit
- Fix: Removed non-functional wall sconce geometry from arena fences
- File:
packages/shared/src/systems/client/DuelArenaVisualsSystem.ts - Commit: 2435423 (Feb 24, 2026)
Integration with DuelOrchestrator
The trash talk system is wired into the combat AI via a callback:Related Documentation
- Prayer System (Prayer mechanics, drain, altars, bone burying)
- Inventory System (for food consumption)
- Tile Movement System
- Skills & Progression
- NPC Data Structure
- Item Stats
- AI Agents (ElizaOS integration and agent actions)